From 861cfe2c0a2e6955c0f3e1d62f6963c160998d9e Mon Sep 17 00:00:00 2001 From: Marek Wolan Date: Fri, 11 Oct 2024 15:00:26 +0100 Subject: [PATCH 01/58] #2912 - scaffold of action changes --- src/primaite/game/agent/actions.py | 573 +------------------- src/primaite/game/agent/actions/__init__.py | 0 src/primaite/game/agent/actions/manager.py | 487 +++++++++++++++++ src/primaite/game/agent/actions/service.py | 42 ++ 4 files changed, 534 insertions(+), 568 deletions(-) create mode 100644 src/primaite/game/agent/actions/__init__.py create mode 100644 src/primaite/game/agent/actions/manager.py create mode 100644 src/primaite/game/agent/actions/service.py diff --git a/src/primaite/game/agent/actions.py b/src/primaite/game/agent/actions.py index 2e6189c0..1df25d27 100644 --- a/src/primaite/game/agent/actions.py +++ b/src/primaite/game/agent/actions.py @@ -9,91 +9,19 @@ AbstractAction. The ActionManager is responsible for: 3. Converting an action and parameter choice into a request which can be ingested by the PrimAITE simulation. This ensures that requests conform to the simulator's request format. """ -import itertools -from abc import ABC, abstractmethod -from typing import Dict, List, Literal, Optional, Tuple, TYPE_CHECKING, Union +from abc import abstractmethod +from typing import Dict, List, Literal, Optional, TYPE_CHECKING, Union -from gymnasium import spaces from pydantic import BaseModel, ConfigDict, Field, field_validator, ValidationInfo from primaite import getLogger +from primaite.game.agent.actions.manager import ActionManager +from primaite.game.agent.actions.manager import AbstractAction +from primaite.game.agent.actions.service import NodeServiceAbstractAction from primaite.interface.request import RequestFormat _LOGGER = getLogger(__name__) -if TYPE_CHECKING: - from primaite.game.game import PrimaiteGame - - -class AbstractAction(ABC): - """Base class for actions.""" - - @abstractmethod - def __init__(self, manager: "ActionManager", **kwargs) -> None: - """ - Init method for action. - - All action init functions should accept **kwargs as a way of ignoring extra arguments. - - Since many parameters are defined for the action space as a whole (such as max files per folder, max services - per node), we need to pass those options to every action that gets created. To prevent verbosity, these - parameters are just broadcasted to all actions and the actions can pay attention to the ones that apply. - """ - self.name: str = "" - """Human-readable action identifier used for printing, logging, and reporting.""" - self.shape: Dict[str, int] = {} - """Dictionary describing the number of options for each parameter of this action. The keys of this dict must - align with the keyword args of the form_request method.""" - self.manager: ActionManager = manager - """Reference to the ActionManager which created this action. This is used to access the game and simulation - objects.""" - - @abstractmethod - def form_request(self) -> RequestFormat: - """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - return [] - - -class DoNothingAction(AbstractAction): - """Action which does nothing. This is here to allow agents to be idle if they choose to.""" - - def __init__(self, manager: "ActionManager", **kwargs) -> None: - super().__init__(manager=manager) - self.name = "DONOTHING" - self.shape: Dict[str, int] = { - "dummy": 1, - } - # This action does not accept any parameters, therefore it technically has a gymnasium shape of Discrete(1), - # i.e. a choice between one option. To make enumerating this action easier, we are adding a 'dummy' paramter - # with one option. This just aids the Action Manager to enumerate all possibilities. - - def form_request(self, **kwargs) -> RequestFormat: - """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - return ["do_nothing"] - - -class NodeServiceAbstractAction(AbstractAction): - """ - Base class for service actions. - - Any action which applies to a service and uses node_id and service_id as its only two parameters can inherit from - this base class. - """ - - @abstractmethod - def __init__(self, manager: "ActionManager", num_nodes: int, num_services: int, **kwargs) -> None: - super().__init__(manager=manager) - self.shape: Dict[str, int] = {"node_id": num_nodes, "service_id": num_services} - self.verb: str # define but don't initialise: defends against children classes not defining this - - def form_request(self, node_id: int, service_id: int) -> RequestFormat: - """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - node_name = self.manager.get_node_name_by_idx(node_id) - service_name = self.manager.get_service_name_by_idx(node_id, service_id) - if node_name is None or service_name is None: - return ["do_nothing"] - return ["network", "node", node_name, "service", service_name, self.verb] - class NodeServiceScanAction(NodeServiceAbstractAction): """Action which scans a service.""" @@ -1311,494 +1239,3 @@ class RansomwareLaunchC2ServerAction(AbstractAction): # This action currently doesn't require any further configuration options. return ["network", "node", node_name, "application", "C2Server", "ransomware_launch"] - -class ActionManager: - """Class which manages the action space for an agent.""" - - act_class_identifiers: Dict[str, type] = { - "DONOTHING": DoNothingAction, - "NODE_SERVICE_SCAN": NodeServiceScanAction, - "NODE_SERVICE_STOP": NodeServiceStopAction, - "NODE_SERVICE_START": NodeServiceStartAction, - "NODE_SERVICE_PAUSE": NodeServicePauseAction, - "NODE_SERVICE_RESUME": NodeServiceResumeAction, - "NODE_SERVICE_RESTART": NodeServiceRestartAction, - "NODE_SERVICE_DISABLE": NodeServiceDisableAction, - "NODE_SERVICE_ENABLE": NodeServiceEnableAction, - "NODE_SERVICE_FIX": NodeServiceFixAction, - "NODE_APPLICATION_EXECUTE": NodeApplicationExecuteAction, - "NODE_APPLICATION_SCAN": NodeApplicationScanAction, - "NODE_APPLICATION_CLOSE": NodeApplicationCloseAction, - "NODE_APPLICATION_FIX": NodeApplicationFixAction, - "NODE_APPLICATION_INSTALL": NodeApplicationInstallAction, - "NODE_APPLICATION_REMOVE": NodeApplicationRemoveAction, - "NODE_FILE_SCAN": NodeFileScanAction, - "NODE_FILE_CREATE": NodeFileCreateAction, - "NODE_FILE_CHECKHASH": NodeFileCheckhashAction, - "NODE_FILE_DELETE": NodeFileDeleteAction, - "NODE_FILE_REPAIR": NodeFileRepairAction, - "NODE_FILE_RESTORE": NodeFileRestoreAction, - "NODE_FILE_CORRUPT": NodeFileCorruptAction, - "NODE_FILE_ACCESS": NodeFileAccessAction, - "NODE_FOLDER_CREATE": NodeFolderCreateAction, - "NODE_FOLDER_SCAN": NodeFolderScanAction, - "NODE_FOLDER_CHECKHASH": NodeFolderCheckhashAction, - "NODE_FOLDER_REPAIR": NodeFolderRepairAction, - "NODE_FOLDER_RESTORE": NodeFolderRestoreAction, - "NODE_OS_SCAN": NodeOSScanAction, - "NODE_SHUTDOWN": NodeShutdownAction, - "NODE_STARTUP": NodeStartupAction, - "NODE_RESET": NodeResetAction, - "ROUTER_ACL_ADDRULE": RouterACLAddRuleAction, - "ROUTER_ACL_REMOVERULE": RouterACLRemoveRuleAction, - "FIREWALL_ACL_ADDRULE": FirewallACLAddRuleAction, - "FIREWALL_ACL_REMOVERULE": FirewallACLRemoveRuleAction, - "HOST_NIC_ENABLE": HostNICEnableAction, - "HOST_NIC_DISABLE": HostNICDisableAction, - "NETWORK_PORT_ENABLE": NetworkPortEnableAction, - "NETWORK_PORT_DISABLE": NetworkPortDisableAction, - "NODE_NMAP_PING_SCAN": NodeNMAPPingScanAction, - "NODE_NMAP_PORT_SCAN": NodeNMAPPortScanAction, - "NODE_NMAP_NETWORK_SERVICE_RECON": NodeNetworkServiceReconAction, - "CONFIGURE_DATABASE_CLIENT": ConfigureDatabaseClientAction, - "CONFIGURE_RANSOMWARE_SCRIPT": ConfigureRansomwareScriptAction, - "CONFIGURE_DOSBOT": ConfigureDoSBotAction, - "CONFIGURE_C2_BEACON": ConfigureC2BeaconAction, - "C2_SERVER_RANSOMWARE_LAUNCH": RansomwareLaunchC2ServerAction, - "C2_SERVER_RANSOMWARE_CONFIGURE": RansomwareConfigureC2ServerAction, - "C2_SERVER_TERMINAL_COMMAND": TerminalC2ServerAction, - "C2_SERVER_DATA_EXFILTRATE": ExfiltrationC2ServerAction, - "NODE_ACCOUNTS_CHANGE_PASSWORD": NodeAccountsChangePasswordAction, - "SSH_TO_REMOTE": NodeSessionsRemoteLoginAction, - "SESSIONS_REMOTE_LOGOFF": NodeSessionsRemoteLogoutAction, - "NODE_SEND_REMOTE_COMMAND": NodeSendRemoteCommandAction, - } - """Dictionary which maps action type strings to the corresponding action class.""" - - def __init__( - self, - actions: List[Dict], # stores list of actions available to agent - nodes: List[Dict], # extra configuration for each node - max_folders_per_node: int = 2, # allows calculating shape - max_files_per_folder: int = 2, # allows calculating shape - max_services_per_node: int = 2, # allows calculating shape - max_applications_per_node: int = 2, # allows calculating shape - max_nics_per_node: int = 8, # allows calculating shape - max_acl_rules: int = 10, # allows calculating shape - protocols: List[str] = ["TCP", "UDP", "ICMP"], # allow mapping index to protocol - ports: List[str] = ["HTTP", "DNS", "ARP", "FTP", "NTP"], # allow mapping index to port - ip_list: List[str] = [], # to allow us to map an index to an ip address. - wildcard_list: List[str] = [], # to allow mapping from wildcard index to - act_map: Optional[Dict[int, Dict]] = None, # allows restricting set of possible actions - ) -> None: - """Init method for ActionManager. - - :param game: Reference to the game to which the agent belongs. - :type game: PrimaiteGame - :param actions: List of action specs which should be made available to the agent. The keys of each spec are: - 'type' and 'options' for passing any options to the action class's init method - :type actions: List[dict] - :param nodes: Extra configuration for each node. - :type nodes: List[Dict] - :param max_folders_per_node: Maximum number of folders per node. Used for calculating action shape. - :type max_folders_per_node: int - :param max_files_per_folder: Maximum number of files per folder. Used for calculating action shape. - :type max_files_per_folder: int - :param max_services_per_node: Maximum number of services per node. Used for calculating action shape. - :type max_services_per_node: int - :param max_nics_per_node: Maximum number of NICs per node. Used for calculating action shape. - :type max_nics_per_node: int - :param max_acl_rules: Maximum number of ACL rules per router. Used for calculating action shape. - :type max_acl_rules: int - :param protocols: List of protocols that are available in the simulation. Used for calculating action shape. - :type protocols: List[str] - :param ports: List of ports that are available in the simulation. Used for calculating action shape. - :type ports: List[str] - :param ip_list: List of IP addresses that known to this agent. Used for calculating action shape. - :type ip_list: Optional[List[str]] - :param act_map: Action map which maps integers to actions. Used for restricting the set of possible actions. - :type act_map: Optional[Dict[int, Dict]] - """ - self.node_names: List[str] = [n["node_name"] for n in nodes] - """List of node names in this action space. The list order is the mapping between node index and node name.""" - self.application_names: List[List[str]] = [] - """ - List of applications per node. The list order gives the two-index mapping between (node_id, app_id) to app name. - The first index corresponds to node id, the second index is the app id on that particular node. - For instance, self.application_names[0][2] is the name of the third application on the first node. - """ - self.service_names: List[List[str]] = [] - """ - List of services per node. The list order gives the two-index mapping between (node_id, svc_id) to svc name. - The first index corresponds to node id, the second index is the service id on that particular node. - For instance, self.service_names[0][2] is the name of the third service on the first node. - """ - self.folder_names: List[List[str]] = [] - """ - List of folders per node. The list order gives the two-index mapping between (node_id, folder_id) to folder - name. The first index corresponds to node id, the second index is the folder id on that particular node. - For instance, self.folder_names[0][2] is the name of the third folder on the first node. - """ - self.file_names: List[List[List[str]]] = [] - """ - List of files per folder per node. The list order gives the three-index mapping between - (node_id, folder_id, file_id) to file name. The first index corresponds to node id, the second index is the - folder id on that particular node, and the third index is the file id in that particular folder. - For instance, self.file_names[0][2][1] is the name of the second file in the third folder on the first node. - """ - - # Populate lists of apps, services, files, folders, etc on nodes. - for node in nodes: - app_list = [a["application_name"] for a in node.get("applications", [])] - while len(app_list) < max_applications_per_node: - app_list.append(None) - self.application_names.append(app_list) - - svc_list = [s["service_name"] for s in node.get("services", [])] - while len(svc_list) < max_services_per_node: - svc_list.append(None) - self.service_names.append(svc_list) - - folder_list = [f["folder_name"] for f in node.get("folders", [])] - while len(folder_list) < max_folders_per_node: - folder_list.append(None) - self.folder_names.append(folder_list) - - file_sublist = [] - for folder in node.get("folders", [{"files": []}]): - file_list = [f["file_name"] for f in folder.get("files", [])] - while len(file_list) < max_files_per_folder: - file_list.append(None) - file_sublist.append(file_list) - while len(file_sublist) < max_folders_per_node: - file_sublist.append([None] * max_files_per_folder) - self.file_names.append(file_sublist) - self.protocols: List[str] = protocols - self.ports: List[str] = ports - - self.ip_address_list: List[str] = ip_list - self.wildcard_list: List[str] = wildcard_list - if self.wildcard_list == []: - self.wildcard_list = ["NONE"] - # action_args are settings which are applied to the action space as a whole. - global_action_args = { - "num_nodes": len(self.node_names), - "num_folders": max_folders_per_node, - "num_files": max_files_per_folder, - "num_services": max_services_per_node, - "num_applications": max_applications_per_node, - "num_nics": max_nics_per_node, - "num_acl_rules": max_acl_rules, - "num_protocols": len(self.protocols), - "num_ports": len(self.protocols), - "num_ips": len(self.ip_address_list), - "max_acl_rules": max_acl_rules, - "max_nics_per_node": max_nics_per_node, - } - self.actions: Dict[str, AbstractAction] = {} - for act_spec in actions: - # each action is provided into the action space config like this: - # - type: ACTION_TYPE - # options: - # option_1: value1 - # option_2: value2 - # where `type` decides which AbstractAction subclass should be used - # and `options` is an optional dict of options to pass to the init method of the action class - act_type = act_spec.get("type") - act_options = act_spec.get("options", {}) - self.actions[act_type] = self.act_class_identifiers[act_type](self, **global_action_args, **act_options) - - self.action_map: Dict[int, Tuple[str, Dict]] = {} - """ - Action mapping that converts an integer to a specific action and parameter choice. - - For example : - {0: ("NODE_SERVICE_SCAN", {node_id:0, service_id:2})} - """ - if act_map is None: - # raise RuntimeError("Action map must be specified in the config file.") - pass - else: - self.action_map = {i: (a["action"], a["options"]) for i, a in act_map.items()} - # make sure all numbers between 0 and N are represented as dict keys in action map - assert all([i in self.action_map.keys() for i in range(len(self.action_map))]) - - def _enumerate_actions( - self, - ) -> Dict[int, Tuple[str, Dict]]: - """Generate a list of all the possible actions that could be taken. - - This enumerates all actions all combinations of parameters you could choose for those actions. The output - of this function is intended to populate the self.action_map parameter in the situation where the user provides - a list of action types, but doesn't specify any subset of actions that should be made available to the agent. - - The enumeration relies on the Actions' `shape` attribute. - - :return: An action map maps consecutive integers to a combination of Action type and parameter choices. - An example output could be: - {0: ("DONOTHING", {'dummy': 0}), - 1: ("NODE_OS_SCAN", {'node_id': 0}), - 2: ("NODE_OS_SCAN", {'node_id': 1}), - 3: ("NODE_FOLDER_SCAN", {'node_id:0, folder_id:0}), - ... #etc... - } - :rtype: Dict[int, Tuple[AbstractAction, Dict]] - """ - all_action_possibilities = [] - for act_name, action in self.actions.items(): - param_names = list(action.shape.keys()) - num_possibilities = list(action.shape.values()) - possibilities = [range(n) for n in num_possibilities] - - param_combinations = list(itertools.product(*possibilities)) - all_action_possibilities.extend( - [ - (act_name, {param_names[i]: param_combinations[j][i] for i in range(len(param_names))}) - for j in range(len(param_combinations)) - ] - ) - - return {i: p for i, p in enumerate(all_action_possibilities)} - - def get_action(self, action: int) -> Tuple[str, Dict]: - """Produce action in CAOS format.""" - """the agent chooses an action (as an integer), this is converted into an action in CAOS format""" - """The CAOS format is basically a action identifier, followed by parameters stored in a dictionary""" - act_identifier, act_options = self.action_map[action] - return act_identifier, act_options - - def form_request(self, action_identifier: str, action_options: Dict) -> RequestFormat: - """Take action in CAOS format and use the execution definition to change it into PrimAITE request format.""" - act_obj = self.actions[action_identifier] - return act_obj.form_request(**action_options) - - @property - def space(self) -> spaces.Space: - """Return the gymnasium action space for this agent.""" - return spaces.Discrete(len(self.action_map)) - - def get_node_name_by_idx(self, node_idx: int) -> str: - """ - Get the node name corresponding to the given index. - - :param node_idx: The index of the node to retrieve. - :type node_idx: int - :return: The node hostname. - :rtype: str - """ - if not node_idx < len(self.node_names): - msg = ( - f"Error: agent attempted to perform an action on node {node_idx}, but its action space only" - f"has {len(self.node_names)} nodes." - ) - _LOGGER.error(msg) - raise RuntimeError(msg) - return self.node_names[node_idx] - - def get_folder_name_by_idx(self, node_idx: int, folder_idx: int) -> Optional[str]: - """ - Get the folder name corresponding to the given node and folder indices. - - :param node_idx: The index of the node. - :type node_idx: int - :param folder_idx: The index of the folder on the node. - :type folder_idx: int - :return: The name of the folder. Or None if the node has fewer folders than the given index. - :rtype: Optional[str] - """ - if node_idx >= len(self.folder_names) or folder_idx >= len(self.folder_names[node_idx]): - msg = ( - f"Error: agent attempted to perform an action on node {node_idx} and folder {folder_idx}, but this" - f" is out of range for its action space. Folder on each node: {self.folder_names}" - ) - _LOGGER.error(msg) - raise RuntimeError(msg) - return self.folder_names[node_idx][folder_idx] - - def get_file_name_by_idx(self, node_idx: int, folder_idx: int, file_idx: int) -> Optional[str]: - """Get the file name corresponding to the given node, folder, and file indices. - - :param node_idx: The index of the node. - :type node_idx: int - :param folder_idx: The index of the folder on the node. - :type folder_idx: int - :param file_idx: The index of the file in the folder. - :type file_idx: int - :return: The name of the file. Or None if the node has fewer folders than the given index, or the folder has - fewer files than the given index. - :rtype: Optional[str] - """ - if ( - node_idx >= len(self.file_names) - or folder_idx >= len(self.file_names[node_idx]) - or file_idx >= len(self.file_names[node_idx][folder_idx]) - ): - msg = ( - f"Error: agent attempted to perform an action on node {node_idx} folder {folder_idx} file {file_idx}" - f" but this is out of range for its action space. Files on each node: {self.file_names}" - ) - _LOGGER.error(msg) - raise RuntimeError(msg) - return self.file_names[node_idx][folder_idx][file_idx] - - def get_service_name_by_idx(self, node_idx: int, service_idx: int) -> Optional[str]: - """Get the service name corresponding to the given node and service indices. - - :param node_idx: The index of the node. - :type node_idx: int - :param service_idx: The index of the service on the node. - :type service_idx: int - :return: The name of the service. Or None if the node has fewer services than the given index. - :rtype: Optional[str] - """ - if node_idx >= len(self.service_names) or service_idx >= len(self.service_names[node_idx]): - msg = ( - f"Error: agent attempted to perform an action on node {node_idx} and service {service_idx}, but this" - f" is out of range for its action space. Services on each node: {self.service_names}" - ) - _LOGGER.error(msg) - raise RuntimeError(msg) - return self.service_names[node_idx][service_idx] - - def get_application_name_by_idx(self, node_idx: int, application_idx: int) -> Optional[str]: - """Get the application name corresponding to the given node and service indices. - - :param node_idx: The index of the node. - :type node_idx: int - :param application_idx: The index of the service on the node. - :type application_idx: int - :return: The name of the service. Or None if the node has fewer services than the given index. - :rtype: Optional[str] - """ - if node_idx >= len(self.application_names) or application_idx >= len(self.application_names[node_idx]): - msg = ( - f"Error: agent attempted to perform an action on node {node_idx} and app {application_idx}, but " - f"this is out of range for its action space. Applications on each node: {self.application_names}" - ) - _LOGGER.error(msg) - raise RuntimeError(msg) - return self.application_names[node_idx][application_idx] - - def get_internet_protocol_by_idx(self, protocol_idx: int) -> str: - """Get the internet protocol corresponding to the given index. - - :param protocol_idx: The index of the protocol to retrieve. - :type protocol_idx: int - :return: The protocol. - :rtype: str - """ - if protocol_idx >= len(self.protocols): - msg = ( - f"Error: agent attempted to perform an action on protocol {protocol_idx} but this" - f" is out of range for its action space. Protocols: {self.protocols}" - ) - _LOGGER.error(msg) - raise RuntimeError(msg) - return self.protocols[protocol_idx] - - def get_ip_address_by_idx(self, ip_idx: int) -> str: - """ - Get the IP address corresponding to the given index. - - :param ip_idx: The index of the IP address to retrieve. - :type ip_idx: int - :return: The IP address. - :rtype: str - """ - if ip_idx >= len(self.ip_address_list): - msg = ( - f"Error: agent attempted to perform an action on ip address {ip_idx} but this" - f" is out of range for its action space. IP address list: {self.ip_address_list}" - ) - _LOGGER.error(msg) - raise RuntimeError(msg) - return self.ip_address_list[ip_idx] - - def get_wildcard_by_idx(self, wildcard_idx: int) -> str: - """ - Get the IP wildcard corresponding to the given index. - - :param ip_idx: The index of the IP wildcard to retrieve. - :type ip_idx: int - :return: The wildcard address. - :rtype: str - """ - if wildcard_idx >= len(self.wildcard_list): - msg = ( - f"Error: agent attempted to perform an action on ip wildcard {wildcard_idx} but this" - f" is out of range for its action space. Wildcard list: {self.wildcard_list}" - ) - _LOGGER.error(msg) - raise RuntimeError(msg) - return self.wildcard_list[wildcard_idx] - - def get_port_by_idx(self, port_idx: int) -> str: - """ - Get the port corresponding to the given index. - - :param port_idx: The index of the port to retrieve. - :type port_idx: int - :return: The port. - :rtype: str - """ - if port_idx >= len(self.ports): - msg = ( - f"Error: agent attempted to perform an action on port {port_idx} but this" - f" is out of range for its action space. Port list: {self.ip_address_list}" - ) - _LOGGER.error(msg) - raise RuntimeError(msg) - return self.ports[port_idx] - - def get_nic_num_by_idx(self, node_idx: int, nic_idx: int) -> int: - """ - Get the NIC number corresponding to the given node and NIC indices. - - :param node_idx: The index of the node. - :type node_idx: int - :param nic_idx: The index of the NIC on the node. - :type nic_idx: int - :return: The NIC number. - :rtype: int - """ - return nic_idx + 1 - - @classmethod - def from_config(cls, game: "PrimaiteGame", cfg: Dict) -> "ActionManager": - """ - Construct an ActionManager from a config definition. - - The action space config supports the following three sections: - 1. ``action_list`` - ``action_list`` contains a list action components which need to be included in the action space. - Each action component has a ``type`` which maps to a subclass of AbstractAction, and additional options - which will be passed to the action class's __init__ method during initialisation. - 2. ``action_map`` - Since the agent uses a discrete action space which acts as a flattened version of the component-based - action space, action_map provides a mapping between an integer (chosen by the agent) and a meaningful - action and values of parameters. For example action 0 can correspond to do nothing, action 1 can - correspond to "NODE_SERVICE_SCAN" with ``node_id=1`` and ``service_id=1``, action 2 can be " - 3. ``options`` - ``options`` contains a dictionary of options which are passed to the ActionManager's __init__ method. - These options are used to calculate the shape of the action space, and to provide additional information - to the ActionManager which is required to convert the agent's action choice into a CAOS request. - - :param game: The Primaite Game to which the agent belongs. - :type game: PrimaiteGame - :param cfg: The action space config. - :type cfg: Dict - :return: The constructed ActionManager. - :rtype: ActionManager - """ - if "ip_list" not in cfg["options"]: - cfg["options"]["ip_list"] = [] - - obj = cls( - actions=cfg["action_list"], - **cfg["options"], - protocols=game.options.protocols, - ports=game.options.ports, - act_map=cfg.get("action_map"), - ) - - return obj diff --git a/src/primaite/game/agent/actions/__init__.py b/src/primaite/game/agent/actions/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/primaite/game/agent/actions/manager.py b/src/primaite/game/agent/actions/manager.py new file mode 100644 index 00000000..34c7c4d6 --- /dev/null +++ b/src/primaite/game/agent/actions/manager.py @@ -0,0 +1,487 @@ +"""yaml example + +agents: + - name: agent_1 + action_space: + actions: + - do_nothing + - node_service_start + - node_service_stop + action_map: +""" + +from abc import ABC, abstractmethod + +from pydantic import BaseModel, ConfigDict +from primaite.game.game import PrimaiteGame +from primaite.interface.request import RequestFormat +from __future__ import annotations +from gymnasium import spaces + + +import itertools +from typing import Any, ClassVar, Dict, List, Literal, Tuple, Type + +class AbstractAction(BaseModel): + """Base class for actions.""" + # notes: + # we actually don't need to hold any state in actions, so there's no need to define any __init__ logic. + # all the init methods in the old actions are just used for holding a verb and shape, which are not really used. + # the config schema should be used to the actual parameters for formatting the action itself. + # (therefore there's no need for creating action instances, just the action class contains logic for converting + # CAOS actions to requests for simulator. Similar to the network node adder, that class also doesn't need to be + # instantiated.) + class ConfigSchema(BaseModel, ABC): # TODO: not sure if this better named something like `Options` + model_config = ConfigDict(extra="forbid") + type: str + + _registry: ClassVar[Dict[str,Type[AbstractAction]]] = {} + + def __init_subclass__(cls, identifier:str, **kwargs: Any) -> None: + super().__init_subclass__(**kwargs) + if identifier in cls._registry: + raise ValueError(f"Cannot create new action under reserved name {identifier}") + cls._registry[identifier] = cls + + @classmethod + def form_request(self, config:ConfigSchema) -> RequestFormat: + """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" + return [] + +class DoNothingAction(AbstractAction): + class ConfigSchema(AbstractAction.ConfigSchema): + type: Literal["do_nothing"] = "do_nothing" + + def form_request(self, options:ConfigSchema) -> RequestFormat: + return ["do_nothing"] + +class ActionManager: + """Class which manages the action space for an agent.""" + + def __init__( + self, + actions: List[Dict], # stores list of actions available to agent + # nodes: List[Dict], # extra configuration for each node + # max_folders_per_node: int = 2, # allows calculating shape + # max_files_per_folder: int = 2, # allows calculating shape + # max_services_per_node: int = 2, # allows calculating shape + # max_applications_per_node: int = 2, # allows calculating shape + # max_nics_per_node: int = 8, # allows calculating shape + # max_acl_rules: int = 10, # allows calculating shape + # protocols: List[str] = ["TCP", "UDP", "ICMP"], # allow mapping index to protocol + # ports: List[str] = ["HTTP", "DNS", "ARP", "FTP", "NTP"], # allow mapping index to port + # ip_list: List[str] = [], # to allow us to map an index to an ip address. + # wildcard_list: List[str] = [], # to allow mapping from wildcard index to + act_map: Optional[Dict[int, Dict]] = None, # allows restricting set of possible actions + ) -> None: + """Init method for ActionManager. + + :param game: Reference to the game to which the agent belongs. + :type game: PrimaiteGame + :param actions: List of action specs which should be made available to the agent. The keys of each spec are: + 'type' and 'options' for passing any options to the action class's init method + :type actions: List[dict] + :param nodes: Extra configuration for each node. + :type nodes: List[Dict] + :param max_folders_per_node: Maximum number of folders per node. Used for calculating action shape. + :type max_folders_per_node: int + :param max_files_per_folder: Maximum number of files per folder. Used for calculating action shape. + :type max_files_per_folder: int + :param max_services_per_node: Maximum number of services per node. Used for calculating action shape. + :type max_services_per_node: int + :param max_nics_per_node: Maximum number of NICs per node. Used for calculating action shape. + :type max_nics_per_node: int + :param max_acl_rules: Maximum number of ACL rules per router. Used for calculating action shape. + :type max_acl_rules: int + :param protocols: List of protocols that are available in the simulation. Used for calculating action shape. + :type protocols: List[str] + :param ports: List of ports that are available in the simulation. Used for calculating action shape. + :type ports: List[str] + :param ip_list: List of IP addresses that known to this agent. Used for calculating action shape. + :type ip_list: Optional[List[str]] + :param act_map: Action map which maps integers to actions. Used for restricting the set of possible actions. + :type act_map: Optional[Dict[int, Dict]] + """ + self.node_names: List[str] = [n["node_name"] for n in nodes] + """List of node names in this action space. The list order is the mapping between node index and node name.""" + self.application_names: List[List[str]] = [] + """ + List of applications per node. The list order gives the two-index mapping between (node_id, app_id) to app name. + The first index corresponds to node id, the second index is the app id on that particular node. + For instance, self.application_names[0][2] is the name of the third application on the first node. + """ + self.service_names: List[List[str]] = [] + """ + List of services per node. The list order gives the two-index mapping between (node_id, svc_id) to svc name. + The first index corresponds to node id, the second index is the service id on that particular node. + For instance, self.service_names[0][2] is the name of the third service on the first node. + """ + self.folder_names: List[List[str]] = [] + """ + List of folders per node. The list order gives the two-index mapping between (node_id, folder_id) to folder + name. The first index corresponds to node id, the second index is the folder id on that particular node. + For instance, self.folder_names[0][2] is the name of the third folder on the first node. + """ + self.file_names: List[List[List[str]]] = [] + """ + List of files per folder per node. The list order gives the three-index mapping between + (node_id, folder_id, file_id) to file name. The first index corresponds to node id, the second index is the + folder id on that particular node, and the third index is the file id in that particular folder. + For instance, self.file_names[0][2][1] is the name of the second file in the third folder on the first node. + """ + + # Populate lists of apps, services, files, folders, etc on nodes. + for node in nodes: + app_list = [a["application_name"] for a in node.get("applications", [])] + while len(app_list) < max_applications_per_node: + app_list.append(None) + self.application_names.append(app_list) + + svc_list = [s["service_name"] for s in node.get("services", [])] + while len(svc_list) < max_services_per_node: + svc_list.append(None) + self.service_names.append(svc_list) + + folder_list = [f["folder_name"] for f in node.get("folders", [])] + while len(folder_list) < max_folders_per_node: + folder_list.append(None) + self.folder_names.append(folder_list) + + file_sublist = [] + for folder in node.get("folders", [{"files": []}]): + file_list = [f["file_name"] for f in folder.get("files", [])] + while len(file_list) < max_files_per_folder: + file_list.append(None) + file_sublist.append(file_list) + while len(file_sublist) < max_folders_per_node: + file_sublist.append([None] * max_files_per_folder) + self.file_names.append(file_sublist) + self.protocols: List[str] = protocols + self.ports: List[str] = ports + + self.ip_address_list: List[str] = ip_list + self.wildcard_list: List[str] = wildcard_list + if self.wildcard_list == []: + self.wildcard_list = ["NONE"] + # action_args are settings which are applied to the action space as a whole. + global_action_args = { + "num_nodes": len(self.node_names), + "num_folders": max_folders_per_node, + "num_files": max_files_per_folder, + "num_services": max_services_per_node, + "num_applications": max_applications_per_node, + "num_nics": max_nics_per_node, + "num_acl_rules": max_acl_rules, + "num_protocols": len(self.protocols), + "num_ports": len(self.protocols), + "num_ips": len(self.ip_address_list), + "max_acl_rules": max_acl_rules, + "max_nics_per_node": max_nics_per_node, + } + self.actions: Dict[str, AbstractAction] = {} + for act_spec in actions: + # each action is provided into the action space config like this: + # - type: ACTION_TYPE + # options: + # option_1: value1 + # option_2: value2 + # where `type` decides which AbstractAction subclass should be used + # and `options` is an optional dict of options to pass to the init method of the action class + act_type = act_spec.get("type") + act_options = act_spec.get("options", {}) + self.actions[act_type] = self.act_class_identifiers[act_type](self, **global_action_args, **act_options) + + self.action_map: Dict[int, Tuple[str, Dict]] = {} + """ + Action mapping that converts an integer to a specific action and parameter choice. + + For example : + {0: ("NODE_SERVICE_SCAN", {node_id:0, service_id:2})} + """ + if act_map is None: + # raise RuntimeError("Action map must be specified in the config file.") + pass + else: + self.action_map = {i: (a["action"], a["options"]) for i, a in act_map.items()} + # make sure all numbers between 0 and N are represented as dict keys in action map + assert all([i in self.action_map.keys() for i in range(len(self.action_map))]) + + def _enumerate_actions( + self, + ) -> Dict[int, Tuple[str, Dict]]: + """Generate a list of all the possible actions that could be taken. + + This enumerates all actions all combinations of parameters you could choose for those actions. The output + of this function is intended to populate the self.action_map parameter in the situation where the user provides + a list of action types, but doesn't specify any subset of actions that should be made available to the agent. + + The enumeration relies on the Actions' `shape` attribute. + + :return: An action map maps consecutive integers to a combination of Action type and parameter choices. + An example output could be: + {0: ("DONOTHING", {'dummy': 0}), + 1: ("NODE_OS_SCAN", {'node_id': 0}), + 2: ("NODE_OS_SCAN", {'node_id': 1}), + 3: ("NODE_FOLDER_SCAN", {'node_id:0, folder_id:0}), + ... #etc... + } + :rtype: Dict[int, Tuple[AbstractAction, Dict]] + """ + all_action_possibilities = [] + for act_name, action in self.actions.items(): + param_names = list(action.shape.keys()) + num_possibilities = list(action.shape.values()) + possibilities = [range(n) for n in num_possibilities] + + param_combinations = list(itertools.product(*possibilities)) + all_action_possibilities.extend( + [ + (act_name, {param_names[i]: param_combinations[j][i] for i in range(len(param_names))}) + for j in range(len(param_combinations)) + ] + ) + + return {i: p for i, p in enumerate(all_action_possibilities)} + + def get_action(self, action: int) -> Tuple[str, Dict]: + """Produce action in CAOS format.""" + """the agent chooses an action (as an integer), this is converted into an action in CAOS format""" + """The CAOS format is basically a action identifier, followed by parameters stored in a dictionary""" + act_identifier, act_options = self.action_map[action] + return act_identifier, act_options + + def form_request(self, action_identifier: str, action_options: Dict) -> RequestFormat: + """Take action in CAOS format and use the execution definition to change it into PrimAITE request format.""" + act_obj = self.actions[action_identifier] + return act_obj.form_request(**action_options) + + @property + def space(self) -> spaces.Space: + """Return the gymnasium action space for this agent.""" + return spaces.Discrete(len(self.action_map)) + + def get_node_name_by_idx(self, node_idx: int) -> str: + """ + Get the node name corresponding to the given index. + + :param node_idx: The index of the node to retrieve. + :type node_idx: int + :return: The node hostname. + :rtype: str + """ + if not node_idx < len(self.node_names): + msg = ( + f"Error: agent attempted to perform an action on node {node_idx}, but its action space only" + f"has {len(self.node_names)} nodes." + ) + _LOGGER.error(msg) + raise RuntimeError(msg) + return self.node_names[node_idx] + + def get_folder_name_by_idx(self, node_idx: int, folder_idx: int) -> Optional[str]: + """ + Get the folder name corresponding to the given node and folder indices. + + :param node_idx: The index of the node. + :type node_idx: int + :param folder_idx: The index of the folder on the node. + :type folder_idx: int + :return: The name of the folder. Or None if the node has fewer folders than the given index. + :rtype: Optional[str] + """ + if node_idx >= len(self.folder_names) or folder_idx >= len(self.folder_names[node_idx]): + msg = ( + f"Error: agent attempted to perform an action on node {node_idx} and folder {folder_idx}, but this" + f" is out of range for its action space. Folder on each node: {self.folder_names}" + ) + _LOGGER.error(msg) + raise RuntimeError(msg) + return self.folder_names[node_idx][folder_idx] + + def get_file_name_by_idx(self, node_idx: int, folder_idx: int, file_idx: int) -> Optional[str]: + """Get the file name corresponding to the given node, folder, and file indices. + + :param node_idx: The index of the node. + :type node_idx: int + :param folder_idx: The index of the folder on the node. + :type folder_idx: int + :param file_idx: The index of the file in the folder. + :type file_idx: int + :return: The name of the file. Or None if the node has fewer folders than the given index, or the folder has + fewer files than the given index. + :rtype: Optional[str] + """ + if ( + node_idx >= len(self.file_names) + or folder_idx >= len(self.file_names[node_idx]) + or file_idx >= len(self.file_names[node_idx][folder_idx]) + ): + msg = ( + f"Error: agent attempted to perform an action on node {node_idx} folder {folder_idx} file {file_idx}" + f" but this is out of range for its action space. Files on each node: {self.file_names}" + ) + _LOGGER.error(msg) + raise RuntimeError(msg) + return self.file_names[node_idx][folder_idx][file_idx] + + def get_service_name_by_idx(self, node_idx: int, service_idx: int) -> Optional[str]: + """Get the service name corresponding to the given node and service indices. + + :param node_idx: The index of the node. + :type node_idx: int + :param service_idx: The index of the service on the node. + :type service_idx: int + :return: The name of the service. Or None if the node has fewer services than the given index. + :rtype: Optional[str] + """ + if node_idx >= len(self.service_names) or service_idx >= len(self.service_names[node_idx]): + msg = ( + f"Error: agent attempted to perform an action on node {node_idx} and service {service_idx}, but this" + f" is out of range for its action space. Services on each node: {self.service_names}" + ) + _LOGGER.error(msg) + raise RuntimeError(msg) + return self.service_names[node_idx][service_idx] + + def get_application_name_by_idx(self, node_idx: int, application_idx: int) -> Optional[str]: + """Get the application name corresponding to the given node and service indices. + + :param node_idx: The index of the node. + :type node_idx: int + :param application_idx: The index of the service on the node. + :type application_idx: int + :return: The name of the service. Or None if the node has fewer services than the given index. + :rtype: Optional[str] + """ + if node_idx >= len(self.application_names) or application_idx >= len(self.application_names[node_idx]): + msg = ( + f"Error: agent attempted to perform an action on node {node_idx} and app {application_idx}, but " + f"this is out of range for its action space. Applications on each node: {self.application_names}" + ) + _LOGGER.error(msg) + raise RuntimeError(msg) + return self.application_names[node_idx][application_idx] + + def get_internet_protocol_by_idx(self, protocol_idx: int) -> str: + """Get the internet protocol corresponding to the given index. + + :param protocol_idx: The index of the protocol to retrieve. + :type protocol_idx: int + :return: The protocol. + :rtype: str + """ + if protocol_idx >= len(self.protocols): + msg = ( + f"Error: agent attempted to perform an action on protocol {protocol_idx} but this" + f" is out of range for its action space. Protocols: {self.protocols}" + ) + _LOGGER.error(msg) + raise RuntimeError(msg) + return self.protocols[protocol_idx] + + def get_ip_address_by_idx(self, ip_idx: int) -> str: + """ + Get the IP address corresponding to the given index. + + :param ip_idx: The index of the IP address to retrieve. + :type ip_idx: int + :return: The IP address. + :rtype: str + """ + if ip_idx >= len(self.ip_address_list): + msg = ( + f"Error: agent attempted to perform an action on ip address {ip_idx} but this" + f" is out of range for its action space. IP address list: {self.ip_address_list}" + ) + _LOGGER.error(msg) + raise RuntimeError(msg) + return self.ip_address_list[ip_idx] + + def get_wildcard_by_idx(self, wildcard_idx: int) -> str: + """ + Get the IP wildcard corresponding to the given index. + + :param ip_idx: The index of the IP wildcard to retrieve. + :type ip_idx: int + :return: The wildcard address. + :rtype: str + """ + if wildcard_idx >= len(self.wildcard_list): + msg = ( + f"Error: agent attempted to perform an action on ip wildcard {wildcard_idx} but this" + f" is out of range for its action space. Wildcard list: {self.wildcard_list}" + ) + _LOGGER.error(msg) + raise RuntimeError(msg) + return self.wildcard_list[wildcard_idx] + + def get_port_by_idx(self, port_idx: int) -> str: + """ + Get the port corresponding to the given index. + + :param port_idx: The index of the port to retrieve. + :type port_idx: int + :return: The port. + :rtype: str + """ + if port_idx >= len(self.ports): + msg = ( + f"Error: agent attempted to perform an action on port {port_idx} but this" + f" is out of range for its action space. Port list: {self.ip_address_list}" + ) + _LOGGER.error(msg) + raise RuntimeError(msg) + return self.ports[port_idx] + + def get_nic_num_by_idx(self, node_idx: int, nic_idx: int) -> int: + """ + Get the NIC number corresponding to the given node and NIC indices. + + :param node_idx: The index of the node. + :type node_idx: int + :param nic_idx: The index of the NIC on the node. + :type nic_idx: int + :return: The NIC number. + :rtype: int + """ + return nic_idx + 1 + + @classmethod + def from_config(cls, game: "PrimaiteGame", cfg: Dict) -> "ActionManager": + """ + Construct an ActionManager from a config definition. + + The action space config supports the following three sections: + 1. ``action_list`` + ``action_list`` contains a list action components which need to be included in the action space. + Each action component has a ``type`` which maps to a subclass of AbstractAction, and additional options + which will be passed to the action class's __init__ method during initialisation. + 2. ``action_map`` + Since the agent uses a discrete action space which acts as a flattened version of the component-based + action space, action_map provides a mapping between an integer (chosen by the agent) and a meaningful + action and values of parameters. For example action 0 can correspond to do nothing, action 1 can + correspond to "NODE_SERVICE_SCAN" with ``node_id=1`` and ``service_id=1``, action 2 can be " + 3. ``options`` + ``options`` contains a dictionary of options which are passed to the ActionManager's __init__ method. + These options are used to calculate the shape of the action space, and to provide additional information + to the ActionManager which is required to convert the agent's action choice into a CAOS request. + + :param game: The Primaite Game to which the agent belongs. + :type game: PrimaiteGame + :param cfg: The action space config. + :type cfg: Dict + :return: The constructed ActionManager. + :rtype: ActionManager + """ + if "ip_list" not in cfg["options"]: + cfg["options"]["ip_list"] = [] + + obj = cls( + actions=cfg["action_list"], + **cfg["options"], + protocols=game.options.protocols, + ports=game.options.ports, + act_map=cfg.get("action_map"), + ) + + return obj diff --git a/src/primaite/game/agent/actions/service.py b/src/primaite/game/agent/actions/service.py new file mode 100644 index 00000000..79d70212 --- /dev/null +++ b/src/primaite/game/agent/actions/service.py @@ -0,0 +1,42 @@ +from typing import ClassVar +from primaite.game.agent.actions.manager import AbstractAction +from primaite.interface.request import RequestFormat + +class NodeServiceAbstractAction(AbstractAction): + class ConfigSchema(AbstractAction.ConfigSchema): + node_name: str + service_name: str + + verb: ClassVar[str] + + @classmethod + def form_request(cls, config:ConfigSchema) -> RequestFormat: + """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" + return ["network", "node", config.node_name, "service", config.service_name, cls.verb] + +class NodeServiceScanAction(NodeServiceAbstractAction, identifier="node_service_scan"): + verb: str = "scan" + +class NodeServiceStopAction(NodeServiceAbstractAction, identifier=...): + verb: str = "stop" + +class NodeServiceStartAction(NodeServiceAbstractAction): + verb: str = "start" + +class NodeServicePauseAction(NodeServiceAbstractAction): + verb: str = "pause" + +class NodeServiceResumeAction(NodeServiceAbstractAction): + verb: str = "resume" + +class NodeServiceRestartAction(NodeServiceAbstractAction): + verb: str = "restart" + +class NodeServiceDisableAction(NodeServiceAbstractAction): + verb: str = "disable" + +class NodeServiceEnableAction(NodeServiceAbstractAction): + verb: str = "enable" + +class NodeServiceFixAction(NodeServiceAbstractAction): + verb: str = "fix" From cd30e2d084dde84d8b63a0b6a51e10b0f529180c Mon Sep 17 00:00:00 2001 From: Charlie Crane Date: Thu, 17 Oct 2024 12:22:30 +0100 Subject: [PATCH 02/58] #2912 - Mid-day commit. Actions moving across from actions.py to game.agent.actions --- src/primaite/game/agent/actions.py | 6 +- src/primaite/game/agent/actions/__init__.py | 27 ++ src/primaite/game/agent/actions/acl.py | 170 +++++++ .../game/agent/actions/application.py | 64 +++ src/primaite/game/agent/actions/file.py | 79 +++ src/primaite/game/agent/actions/folder.py | 65 +++ src/primaite/game/agent/actions/manager.py | 449 +++++++++--------- src/primaite/game/agent/actions/node.py | 52 ++ src/primaite/game/agent/actions/service.py | 75 ++- 9 files changed, 743 insertions(+), 244 deletions(-) create mode 100644 src/primaite/game/agent/actions/acl.py create mode 100644 src/primaite/game/agent/actions/application.py create mode 100644 src/primaite/game/agent/actions/file.py create mode 100644 src/primaite/game/agent/actions/folder.py create mode 100644 src/primaite/game/agent/actions/node.py diff --git a/src/primaite/game/agent/actions.py b/src/primaite/game/agent/actions.py index 1df25d27..68e42fb1 100644 --- a/src/primaite/game/agent/actions.py +++ b/src/primaite/game/agent/actions.py @@ -10,13 +10,12 @@ AbstractAction. The ActionManager is responsible for: ensures that requests conform to the simulator's request format. """ from abc import abstractmethod -from typing import Dict, List, Literal, Optional, TYPE_CHECKING, Union +from typing import Dict, List, Literal, Optional, Union from pydantic import BaseModel, ConfigDict, Field, field_validator, ValidationInfo from primaite import getLogger -from primaite.game.agent.actions.manager import ActionManager -from primaite.game.agent.actions.manager import AbstractAction +from primaite.game.agent.actions.manager import AbstractAction, ActionManager from primaite.game.agent.actions.service import NodeServiceAbstractAction from primaite.interface.request import RequestFormat @@ -1238,4 +1237,3 @@ class RansomwareLaunchC2ServerAction(AbstractAction): return ["do_nothing"] # This action currently doesn't require any further configuration options. return ["network", "node", node_name, "application", "C2Server", "ransomware_launch"] - diff --git a/src/primaite/game/agent/actions/__init__.py b/src/primaite/game/agent/actions/__init__.py index e69de29b..24a3ad67 100644 --- a/src/primaite/game/agent/actions/__init__.py +++ b/src/primaite/game/agent/actions/__init__.py @@ -0,0 +1,27 @@ +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + +from primaite.game.agent.actions.manager import ActionManager +from primaite.game.agent.actions.service import ( + NodeServiceDisableAction, + NodeServiceEnableAction, + NodeServiceFixAction, + NodeServicePauseAction, + NodeServiceRestartAction, + NodeServiceResumeAction, + NodeServiceScanAction, + NodeServiceStartAction, + NodeServiceStopAction, +) + +__all__ = ( + "NodeServiceDisableAction", + "NodeServiceEnableAction", + "NodeServiceFixAction", + "NodeServicePauseAction", + "NodeServiceRestartAction", + "NodeServiceResumeAction", + "NodeServiceScanAction", + "NodeServiceStartAction", + "NodeServiceStopAction", + "ActionManager", +) diff --git a/src/primaite/game/agent/actions/acl.py b/src/primaite/game/agent/actions/acl.py new file mode 100644 index 00000000..22e0a465 --- /dev/null +++ b/src/primaite/game/agent/actions/acl.py @@ -0,0 +1,170 @@ +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +from typing import Dict, List, Literal + +from pydantic import BaseModel, Field, field_validator, ValidationInfo + +from primaite.game.agent.actions.manager import AbstractAction +from primaite.game.game import _LOGGER + + +class RouterACLAddRuleAction(AbstractAction, identifier="router_acl_add_rule"): + """Action which adds a rule to a router's ACL.""" + + class ACLRuleOptions(BaseModel): + """Validator for ACL_ADD_RULE options.""" + + target_router: str + """On which router to add the rule, must be specified.""" + position: int + """At what position to add the rule, must be specified.""" + permission: Literal[1, 2] + """Whether to allow or deny traffic, must be specified. 1 = PERMIT, 2 = DENY.""" + source_ip_id: int = Field(default=1, ge=1) + """Rule source IP address. By default, all ip addresses.""" + source_wildcard_id: int = Field(default=0, ge=0) + """Rule source IP wildcard. By default, use the wildcard at index 0 from action manager.""" + source_port_id: int = Field(default=1, ge=1) + """Rule source port. By default, all source ports.""" + dest_ip_id: int = Field(default=1, ge=1) + """Rule destination IP address. By default, all ip addresses.""" + dest_wildcard_id: int = Field(default=0, ge=0) + """Rule destination IP wildcard. By default, use the wildcard at index 0 from action manager.""" + dest_port_id: int = Field(default=1, ge=1) + """Rule destination port. By default, all destination ports.""" + protocol_id: int = Field(default=1, ge=1) + """Rule protocol. By default, all protocols.""" + + @field_validator( + "source_ip_id", + "source_port_id", + "source_wildcard_id", + "dest_ip_id", + "dest_port_id", + "dest_wildcard_id", + "protocol_id", + mode="before", + ) + @classmethod + def not_none(cls, v: str, info: ValidationInfo) -> int: + """If None is passed, use the default value instead.""" + if v is None: + return cls.model_fields[info.field_name].default + return v + + def __init__( + self, + manager: "ActionManager", + max_acl_rules: int, + num_ips: int, + num_ports: int, + num_protocols: int, + **kwargs, + ) -> None: + """Init method for RouterACLAddRuleAction. + + :param manager: Reference to the ActionManager which created this action. + :type manager: ActionManager + :param max_acl_rules: Maximum number of ACL rules that can be added to the router. + :type max_acl_rules: int + :param num_ips: Number of IP addresses in the simulation. + :type num_ips: int + :param num_ports: Number of ports in the simulation. + :type num_ports: int + :param num_protocols: Number of protocols in the simulation. + :type num_protocols: int + """ + super().__init__(manager=manager) + num_permissions = 3 + self.shape: Dict[str, int] = { + "position": max_acl_rules, + "permission": num_permissions, + "source_ip_id": num_ips, + "dest_ip_id": num_ips, + "source_port_id": num_ports, + "dest_port_id": num_ports, + "protocol_id": num_protocols, + } + + def form_request( + self, + target_router: str, + position: int, + permission: int, + source_ip_id: int, + source_wildcard_id: int, + dest_ip_id: int, + dest_wildcard_id: int, + source_port_id: int, + dest_port_id: int, + protocol_id: int, + ) -> List[str]: + """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" + # Validate incoming data. + parsed_options = RouterACLAddRuleAction.ACLRuleOptions( + target_router=target_router, + position=position, + permission=permission, + source_ip_id=source_ip_id, + source_wildcard_id=source_wildcard_id, + dest_ip_id=dest_ip_id, + dest_wildcard_id=dest_wildcard_id, + source_port_id=source_port_id, + dest_port_id=dest_port_id, + protocol_id=protocol_id, + ) + if parsed_options.permission == 1: + permission_str = "PERMIT" + elif parsed_options.permission == 2: + permission_str = "DENY" + else: + _LOGGER.warning(f"{self.__class__} received permission {permission}, expected 0 or 1.") + + if parsed_options.protocol_id == 1: + protocol = "ALL" + else: + protocol = self.manager.get_internet_protocol_by_idx(parsed_options.protocol_id - 2) + # subtract 2 to account for UNUSED=0 and ALL=1. + + if parsed_options.source_ip_id == 1: + src_ip = "ALL" + else: + src_ip = self.manager.get_ip_address_by_idx(parsed_options.source_ip_id - 2) + # subtract 2 to account for UNUSED=0, and ALL=1 + + src_wildcard = self.manager.get_wildcard_by_idx(parsed_options.source_wildcard_id) + + if parsed_options.source_port_id == 1: + src_port = "ALL" + else: + src_port = self.manager.get_port_by_idx(parsed_options.source_port_id - 2) + # subtract 2 to account for UNUSED=0, and ALL=1 + + if parsed_options.dest_ip_id == 1: + dst_ip = "ALL" + else: + dst_ip = self.manager.get_ip_address_by_idx(parsed_options.dest_ip_id - 2) + # subtract 2 to account for UNUSED=0, and ALL=1 + dst_wildcard = self.manager.get_wildcard_by_idx(parsed_options.dest_wildcard_id) + + if parsed_options.dest_port_id == 1: + dst_port = "ALL" + else: + dst_port = self.manager.get_port_by_idx(parsed_options.dest_port_id - 2) + # subtract 2 to account for UNUSED=0, and ALL=1 + + return [ + "network", + "node", + target_router, + "acl", + "add_rule", + permission_str, + protocol, + str(src_ip), + src_wildcard, + src_port, + str(dst_ip), + dst_wildcard, + dst_port, + position, + ] diff --git a/src/primaite/game/agent/actions/application.py b/src/primaite/game/agent/actions/application.py new file mode 100644 index 00000000..4b82ffd3 --- /dev/null +++ b/src/primaite/game/agent/actions/application.py @@ -0,0 +1,64 @@ +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +from abc import abstractmethod +from typing import ClassVar, Dict + +from primaite.game.agent.actions.manager import AbstractAction +from primaite.interface.request import RequestFormat + + +class NodeApplicationAbstractAction(AbstractAction): + """ + Base class for application actions. + + Any action which applies to an application and uses node_id and application_id as its only two parameters can + inherit from this base class. + """ + + class ConfigSchema(AbstractAction.ConfigSchema): + node_name: str + application_name: str + + verb: ClassVar[str] + + @classmethod + def form_request(cls, config: ConfigSchema) -> RequestFormat: + """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" + if config.node_name is None or config.application_name is None: + return ["do_nothing"] + return ["network", "node", config.node_name, "application", config.application_name, cls.verb] + + +class NodeApplicationExecuteAction(NodeApplicationAbstractAction, identifier="node_application_execute"): + """Action which executes an application.""" + + class ConfigSchema(NodeApplicationAbstractAction.ConfigSchema): + verb: str = "execute" + + +class NodeApplicationScanAction(NodeApplicationAbstractAction, identifier="node_application_scan"): + """Action which scans an application.""" + + class ConfigSchema(NodeApplicationAbstractAction.ConfigSchema): + verb: str = "scan" + + +class NodeApplicationCloseAction(NodeApplicationAbstractAction, identifier="node_application_close"): + """Action which closes an application.""" + + class ConfigSchema(NodeApplicationAbstractAction.ConfigSchema): + verb: str = "close" + + +class NodeApplicationFixAction(NodeApplicationAbstractAction, identifier="node_application_fix"): + """Action which fixes an application.""" + + class ConfigSchema(NodeApplicationAbstractAction.ConfigSchema): + verb: str = "fix" + + +class NodeApplicationInstallAction(AbstractAction): + """Action which installs an application.""" + + class ConfigSchema(NodeApplicationAbstractAction.ConfigSchema): + verb: str = "install" diff --git a/src/primaite/game/agent/actions/file.py b/src/primaite/game/agent/actions/file.py new file mode 100644 index 00000000..d21daa9b --- /dev/null +++ b/src/primaite/game/agent/actions/file.py @@ -0,0 +1,79 @@ +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +from typing import ClassVar + +from primaite.game.agent.actions.manager import AbstractAction +from primaite.interface.request import RequestFormat + + +class NodeFileAbstractAction(AbstractAction): + """Abstract base class for file actions. + + Any action which applies to a file and uses node_name, folder_name, and file_name as its only three parameters can inherit + from this base class. + """ + + class ConfigSchema(AbstractAction.ConfigSchema): + node_name: str + folder_name: str + file_name: str + + verb: ClassVar[str] + + @classmethod + def form_request(cls, config: ConfigSchema) -> RequestFormat: + """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" + if config.node_name is None or config.folder_name is None or config.file_name is None: + return ["do_nothing"] + return [ + "network", + "node", + config.node_name, + "file_system", + "folder", + config.folder_name, + "file", + config.file_name, + cls.verb, + ] + + +class NodeFileCreateAction(NodeFileAbstractAction, identifier="node_file_create"): + """Action which creates a new file in a given folder.""" + + class ConfigSchema(NodeFileAbstractAction.ConfigSchema): + verb: str = "create" + + +class NodeFileScanAction(NodeFileAbstractAction, identifier="node_file_scan"): + """Action which scans a file.""" + + class ConfigSchema(NodeFileAbstractAction.ConfigSchema): + verb: str = "scan" + + +class NodeFileDeleteAction(NodeFileAbstractAction, identifier="node_file_delete"): + """Action which deletes a file.""" + + class ConfigSchema(NodeFileAbstractAction.ConfigSchema): + verb: str = "delete" + + +class NodeFileRestoreAction(NodeFileAbstractAction, identifier="node_file_restore"): + """Action which restores a file.""" + + class ConfigSchema(NodeFileAbstractAction.ConfigSchema): + verb: str = "restore" + + +class NodeFileCorruptAction(NodeFileAbstractAction, identifier="node_file_corrupt"): + """Action which corrupts a file.""" + + class ConfigSchema(NodeFileAbstractAction.ConfigSchema): + verb: str = "corrupt" + + +class NodeFileAccessAction(NodeFileAbstractAction, identifier="node_file_access"): + """Action which increases a file's access count.""" + + class ConfigSchema(NodeFileAbstractAction.ConfigSchema): + verb: str = "access" diff --git a/src/primaite/game/agent/actions/folder.py b/src/primaite/game/agent/actions/folder.py new file mode 100644 index 00000000..278f5658 --- /dev/null +++ b/src/primaite/game/agent/actions/folder.py @@ -0,0 +1,65 @@ +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +from abc import abstractmethod +from typing import ClassVar, Dict + +from primaite.game.agent.actions.manager import AbstractAction +from primaite.interface.request import RequestFormat + + +class NodeFolderAbstractAction(AbstractAction): + """ + Base class for folder actions. + + Any action which applies to a folder and uses node_id and folder_id as its only two parameters can inherit from + this base class. + """ + + class ConfigSchema(AbstractAction.ConfigSchema): + node_name: str + folder_name: str + + verb: ClassVar[str] + + @classmethod + def form_request(cls, node_id: int, folder_id: int) -> RequestFormat: + """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" + node_name = cls.manager.get_node_name_by_idx(node_id) + folder_name = cls.manager.get_folder_name_by_idx(node_idx=node_id, folder_idx=folder_id) + if node_name is None or folder_name is None: + return ["do_nothing"] + return ["network", "node", node_name, "file_system", "folder", folder_name, cls.verb] + + +class NodeFolderScanAction(NodeFolderAbstractAction, identifier="node_folder_scan"): + """Action which scans a folder.""" + + class ConfigSchema(NodeFolderAbstractAction.ConfigSchema): + verb: str = "scan" + + +class NodeFolderCheckhashAction(NodeFolderAbstractAction, identifier="node_folder_checkhash"): + """Action which checks the hash of a folder.""" + + class ConfigSchema(NodeFolderAbstractAction.ConfigSchema): + verb: str = "checkhash" + + +class NodeFolderRepairAction(NodeFolderAbstractAction, identifier="node_folder_repair"): + """Action which repairs a folder.""" + + class ConfigSchema(NodeFolderAbstractAction.ConfigSchema): + verb: str = "repair" + + +class NodeFolderRestoreAction(NodeFolderAbstractAction, identifier="node_folder_restore"): + """Action which restores a folder.""" + + class ConfigSchema(NodeFolderAbstractAction.ConfigSchema): + verb: str = "restore" + + +class NodeFolderCreateAction(AbstractAction, identifier="node_folder_create"): + """Action which creates a new folder.""" + + class ConfigSchema(NodeFolderAbstractAction.ConfigSchema): + verb: str = "create" diff --git a/src/primaite/game/agent/actions/manager.py b/src/primaite/game/agent/actions/manager.py index 34c7c4d6..99ce091e 100644 --- a/src/primaite/game/agent/actions/manager.py +++ b/src/primaite/game/agent/actions/manager.py @@ -1,3 +1,4 @@ +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK """yaml example agents: @@ -10,20 +11,22 @@ agents: action_map: """ -from abc import ABC, abstractmethod - -from pydantic import BaseModel, ConfigDict -from primaite.game.game import PrimaiteGame -from primaite.interface.request import RequestFormat from __future__ import annotations -from gymnasium import spaces - import itertools -from typing import Any, ClassVar, Dict, List, Literal, Tuple, Type +from abc import ABC, abstractmethod +from typing import Any, ClassVar, Dict, List, Literal, Optional, Tuple, Type + +from gymnasium import spaces +from pydantic import BaseModel, ConfigDict + +from primaite.game.game import _LOGGER, PrimaiteGame +from primaite.interface.request import RequestFormat + class AbstractAction(BaseModel): """Base class for actions.""" + # notes: # we actually don't need to hold any state in actions, so there's no need to define any __init__ logic. # all the init methods in the old actions are just used for holding a verb and shape, which are not really used. @@ -31,30 +34,32 @@ class AbstractAction(BaseModel): # (therefore there's no need for creating action instances, just the action class contains logic for converting # CAOS actions to requests for simulator. Similar to the network node adder, that class also doesn't need to be # instantiated.) - class ConfigSchema(BaseModel, ABC): # TODO: not sure if this better named something like `Options` + class ConfigSchema(BaseModel, ABC): # TODO: not sure if this better named something like `Options` model_config = ConfigDict(extra="forbid") type: str - _registry: ClassVar[Dict[str,Type[AbstractAction]]] = {} + _registry: ClassVar[Dict[str, Type[AbstractAction]]] = {} - def __init_subclass__(cls, identifier:str, **kwargs: Any) -> None: + def __init_subclass__(cls, identifier: str, **kwargs: Any) -> None: super().__init_subclass__(**kwargs) if identifier in cls._registry: raise ValueError(f"Cannot create new action under reserved name {identifier}") cls._registry[identifier] = cls @classmethod - def form_request(self, config:ConfigSchema) -> RequestFormat: + def form_request(self, config: ConfigSchema) -> RequestFormat: """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" return [] + class DoNothingAction(AbstractAction): class ConfigSchema(AbstractAction.ConfigSchema): type: Literal["do_nothing"] = "do_nothing" - def form_request(self, options:ConfigSchema) -> RequestFormat: + def form_request(self, options: ConfigSchema) -> RequestFormat: return ["do_nothing"] + class ActionManager: """Class which manages the action space for an agent.""" @@ -131,53 +136,53 @@ class ActionManager: """ # Populate lists of apps, services, files, folders, etc on nodes. - for node in nodes: - app_list = [a["application_name"] for a in node.get("applications", [])] - while len(app_list) < max_applications_per_node: - app_list.append(None) - self.application_names.append(app_list) + # for node in nodes: + # app_list = [a["application_name"] for a in node.get("applications", [])] + # while len(app_list) < max_applications_per_node: + # app_list.append(None) + # self.application_names.append(app_list) - svc_list = [s["service_name"] for s in node.get("services", [])] - while len(svc_list) < max_services_per_node: - svc_list.append(None) - self.service_names.append(svc_list) + # svc_list = [s["service_name"] for s in node.get("services", [])] + # while len(svc_list) < max_services_per_node: + # svc_list.append(None) + # self.service_names.append(svc_list) - folder_list = [f["folder_name"] for f in node.get("folders", [])] - while len(folder_list) < max_folders_per_node: - folder_list.append(None) - self.folder_names.append(folder_list) + # folder_list = [f["folder_name"] for f in node.get("folders", [])] + # while len(folder_list) < max_folders_per_node: + # folder_list.append(None) + # self.folder_names.append(folder_list) - file_sublist = [] - for folder in node.get("folders", [{"files": []}]): - file_list = [f["file_name"] for f in folder.get("files", [])] - while len(file_list) < max_files_per_folder: - file_list.append(None) - file_sublist.append(file_list) - while len(file_sublist) < max_folders_per_node: - file_sublist.append([None] * max_files_per_folder) - self.file_names.append(file_sublist) - self.protocols: List[str] = protocols - self.ports: List[str] = ports + # file_sublist = [] + # for folder in node.get("folders", [{"files": []}]): + # file_list = [f["file_name"] for f in folder.get("files", [])] + # while len(file_list) < max_files_per_folder: + # file_list.append(None) + # file_sublist.append(file_list) + # while len(file_sublist) < max_folders_per_node: + # file_sublist.append([None] * max_files_per_folder) + # self.file_names.append(file_sublist) + # self.protocols: List[str] = protocols + # self.ports: List[str] = ports - self.ip_address_list: List[str] = ip_list - self.wildcard_list: List[str] = wildcard_list - if self.wildcard_list == []: - self.wildcard_list = ["NONE"] - # action_args are settings which are applied to the action space as a whole. - global_action_args = { - "num_nodes": len(self.node_names), - "num_folders": max_folders_per_node, - "num_files": max_files_per_folder, - "num_services": max_services_per_node, - "num_applications": max_applications_per_node, - "num_nics": max_nics_per_node, - "num_acl_rules": max_acl_rules, - "num_protocols": len(self.protocols), - "num_ports": len(self.protocols), - "num_ips": len(self.ip_address_list), - "max_acl_rules": max_acl_rules, - "max_nics_per_node": max_nics_per_node, - } + # self.ip_address_list: List[str] = ip_list + # self.wildcard_list: List[str] = wildcard_list + # if self.wildcard_list == []: + # self.wildcard_list = ["NONE"] + # # action_args are settings which are applied to the action space as a whole. + # global_action_args = { + # "num_nodes": len(self.node_names), + # "num_folders": max_folders_per_node, + # "num_files": max_files_per_folder, + # "num_services": max_services_per_node, + # "num_applications": max_applications_per_node, + # "num_nics": max_nics_per_node, + # "num_acl_rules": max_acl_rules, + # "num_protocols": len(self.protocols), + # "num_ports": len(self.protocols), + # "num_ips": len(self.ip_address_list), + # "max_acl_rules": max_acl_rules, + # "max_nics_per_node": max_nics_per_node, + # } self.actions: Dict[str, AbstractAction] = {} for act_spec in actions: # each action is provided into the action space config like this: @@ -260,191 +265,191 @@ class ActionManager: """Return the gymnasium action space for this agent.""" return spaces.Discrete(len(self.action_map)) - def get_node_name_by_idx(self, node_idx: int) -> str: - """ - Get the node name corresponding to the given index. + # def get_node_name_by_idx(self, node_idx: int) -> str: + # """ + # Get the node name corresponding to the given index. - :param node_idx: The index of the node to retrieve. - :type node_idx: int - :return: The node hostname. - :rtype: str - """ - if not node_idx < len(self.node_names): - msg = ( - f"Error: agent attempted to perform an action on node {node_idx}, but its action space only" - f"has {len(self.node_names)} nodes." - ) - _LOGGER.error(msg) - raise RuntimeError(msg) - return self.node_names[node_idx] + # :param node_idx: The index of the node to retrieve. + # :type node_idx: int + # :return: The node hostname. + # :rtype: str + # """ + # if not node_idx < len(self.node_names): + # msg = ( + # f"Error: agent attempted to perform an action on node {node_idx}, but its action space only" + # f"has {len(self.node_names)} nodes." + # ) + # _LOGGER.error(msg) + # raise RuntimeError(msg) + # return self.node_names[node_idx] - def get_folder_name_by_idx(self, node_idx: int, folder_idx: int) -> Optional[str]: - """ - Get the folder name corresponding to the given node and folder indices. + # def get_folder_name_by_idx(self, node_idx: int, folder_idx: int) -> Optional[str]: + # """ + # Get the folder name corresponding to the given node and folder indices. - :param node_idx: The index of the node. - :type node_idx: int - :param folder_idx: The index of the folder on the node. - :type folder_idx: int - :return: The name of the folder. Or None if the node has fewer folders than the given index. - :rtype: Optional[str] - """ - if node_idx >= len(self.folder_names) or folder_idx >= len(self.folder_names[node_idx]): - msg = ( - f"Error: agent attempted to perform an action on node {node_idx} and folder {folder_idx}, but this" - f" is out of range for its action space. Folder on each node: {self.folder_names}" - ) - _LOGGER.error(msg) - raise RuntimeError(msg) - return self.folder_names[node_idx][folder_idx] + # :param node_idx: The index of the node. + # :type node_idx: int + # :param folder_idx: The index of the folder on the node. + # :type folder_idx: int + # :return: The name of the folder. Or None if the node has fewer folders than the given index. + # :rtype: Optional[str] + # """ + # if node_idx >= len(self.folder_names) or folder_idx >= len(self.folder_names[node_idx]): + # msg = ( + # f"Error: agent attempted to perform an action on node {node_idx} and folder {folder_idx}, but this" + # f" is out of range for its action space. Folder on each node: {self.folder_names}" + # ) + # _LOGGER.error(msg) + # raise RuntimeError(msg) + # return self.folder_names[node_idx][folder_idx] - def get_file_name_by_idx(self, node_idx: int, folder_idx: int, file_idx: int) -> Optional[str]: - """Get the file name corresponding to the given node, folder, and file indices. + # def get_file_name_by_idx(self, node_idx: int, folder_idx: int, file_idx: int) -> Optional[str]: + # """Get the file name corresponding to the given node, folder, and file indices. - :param node_idx: The index of the node. - :type node_idx: int - :param folder_idx: The index of the folder on the node. - :type folder_idx: int - :param file_idx: The index of the file in the folder. - :type file_idx: int - :return: The name of the file. Or None if the node has fewer folders than the given index, or the folder has - fewer files than the given index. - :rtype: Optional[str] - """ - if ( - node_idx >= len(self.file_names) - or folder_idx >= len(self.file_names[node_idx]) - or file_idx >= len(self.file_names[node_idx][folder_idx]) - ): - msg = ( - f"Error: agent attempted to perform an action on node {node_idx} folder {folder_idx} file {file_idx}" - f" but this is out of range for its action space. Files on each node: {self.file_names}" - ) - _LOGGER.error(msg) - raise RuntimeError(msg) - return self.file_names[node_idx][folder_idx][file_idx] + # :param node_idx: The index of the node. + # :type node_idx: int + # :param folder_idx: The index of the folder on the node. + # :type folder_idx: int + # :param file_idx: The index of the file in the folder. + # :type file_idx: int + # :return: The name of the file. Or None if the node has fewer folders than the given index, or the folder has + # fewer files than the given index. + # :rtype: Optional[str] + # """ + # if ( + # node_idx >= len(self.file_names) + # or folder_idx >= len(self.file_names[node_idx]) + # or file_idx >= len(self.file_names[node_idx][folder_idx]) + # ): + # msg = ( + # f"Error: agent attempted to perform an action on node {node_idx} folder {folder_idx} file {file_idx}" + # f" but this is out of range for its action space. Files on each node: {self.file_names}" + # ) + # _LOGGER.error(msg) + # raise RuntimeError(msg) + # return self.file_names[node_idx][folder_idx][file_idx] - def get_service_name_by_idx(self, node_idx: int, service_idx: int) -> Optional[str]: - """Get the service name corresponding to the given node and service indices. + # def get_service_name_by_idx(self, node_idx: int, service_idx: int) -> Optional[str]: + # """Get the service name corresponding to the given node and service indices. - :param node_idx: The index of the node. - :type node_idx: int - :param service_idx: The index of the service on the node. - :type service_idx: int - :return: The name of the service. Or None if the node has fewer services than the given index. - :rtype: Optional[str] - """ - if node_idx >= len(self.service_names) or service_idx >= len(self.service_names[node_idx]): - msg = ( - f"Error: agent attempted to perform an action on node {node_idx} and service {service_idx}, but this" - f" is out of range for its action space. Services on each node: {self.service_names}" - ) - _LOGGER.error(msg) - raise RuntimeError(msg) - return self.service_names[node_idx][service_idx] + # :param node_idx: The index of the node. + # :type node_idx: int + # :param service_idx: The index of the service on the node. + # :type service_idx: int + # :return: The name of the service. Or None if the node has fewer services than the given index. + # :rtype: Optional[str] + # """ + # if node_idx >= len(self.service_names) or service_idx >= len(self.service_names[node_idx]): + # msg = ( + # f"Error: agent attempted to perform an action on node {node_idx} and service {service_idx}, but this" + # f" is out of range for its action space. Services on each node: {self.service_names}" + # ) + # _LOGGER.error(msg) + # raise RuntimeError(msg) + # return self.service_names[node_idx][service_idx] - def get_application_name_by_idx(self, node_idx: int, application_idx: int) -> Optional[str]: - """Get the application name corresponding to the given node and service indices. + # def get_application_name_by_idx(self, node_idx: int, application_idx: int) -> Optional[str]: + # """Get the application name corresponding to the given node and service indices. - :param node_idx: The index of the node. - :type node_idx: int - :param application_idx: The index of the service on the node. - :type application_idx: int - :return: The name of the service. Or None if the node has fewer services than the given index. - :rtype: Optional[str] - """ - if node_idx >= len(self.application_names) or application_idx >= len(self.application_names[node_idx]): - msg = ( - f"Error: agent attempted to perform an action on node {node_idx} and app {application_idx}, but " - f"this is out of range for its action space. Applications on each node: {self.application_names}" - ) - _LOGGER.error(msg) - raise RuntimeError(msg) - return self.application_names[node_idx][application_idx] + # :param node_idx: The index of the node. + # :type node_idx: int + # :param application_idx: The index of the service on the node. + # :type application_idx: int + # :return: The name of the service. Or None if the node has fewer services than the given index. + # :rtype: Optional[str] + # """ + # if node_idx >= len(self.application_names) or application_idx >= len(self.application_names[node_idx]): + # msg = ( + # f"Error: agent attempted to perform an action on node {node_idx} and app {application_idx}, but " + # f"this is out of range for its action space. Applications on each node: {self.application_names}" + # ) + # _LOGGER.error(msg) + # raise RuntimeError(msg) + # return self.application_names[node_idx][application_idx] - def get_internet_protocol_by_idx(self, protocol_idx: int) -> str: - """Get the internet protocol corresponding to the given index. + # def get_internet_protocol_by_idx(self, protocol_idx: int) -> str: + # """Get the internet protocol corresponding to the given index. - :param protocol_idx: The index of the protocol to retrieve. - :type protocol_idx: int - :return: The protocol. - :rtype: str - """ - if protocol_idx >= len(self.protocols): - msg = ( - f"Error: agent attempted to perform an action on protocol {protocol_idx} but this" - f" is out of range for its action space. Protocols: {self.protocols}" - ) - _LOGGER.error(msg) - raise RuntimeError(msg) - return self.protocols[protocol_idx] + # :param protocol_idx: The index of the protocol to retrieve. + # :type protocol_idx: int + # :return: The protocol. + # :rtype: str + # """ + # if protocol_idx >= len(self.protocols): + # msg = ( + # f"Error: agent attempted to perform an action on protocol {protocol_idx} but this" + # f" is out of range for its action space. Protocols: {self.protocols}" + # ) + # _LOGGER.error(msg) + # raise RuntimeError(msg) + # return self.protocols[protocol_idx] - def get_ip_address_by_idx(self, ip_idx: int) -> str: - """ - Get the IP address corresponding to the given index. + # def get_ip_address_by_idx(self, ip_idx: int) -> str: + # """ + # Get the IP address corresponding to the given index. - :param ip_idx: The index of the IP address to retrieve. - :type ip_idx: int - :return: The IP address. - :rtype: str - """ - if ip_idx >= len(self.ip_address_list): - msg = ( - f"Error: agent attempted to perform an action on ip address {ip_idx} but this" - f" is out of range for its action space. IP address list: {self.ip_address_list}" - ) - _LOGGER.error(msg) - raise RuntimeError(msg) - return self.ip_address_list[ip_idx] + # :param ip_idx: The index of the IP address to retrieve. + # :type ip_idx: int + # :return: The IP address. + # :rtype: str + # """ + # if ip_idx >= len(self.ip_address_list): + # msg = ( + # f"Error: agent attempted to perform an action on ip address {ip_idx} but this" + # f" is out of range for its action space. IP address list: {self.ip_address_list}" + # ) + # _LOGGER.error(msg) + # raise RuntimeError(msg) + # return self.ip_address_list[ip_idx] - def get_wildcard_by_idx(self, wildcard_idx: int) -> str: - """ - Get the IP wildcard corresponding to the given index. + # def get_wildcard_by_idx(self, wildcard_idx: int) -> str: + # """ + # Get the IP wildcard corresponding to the given index. - :param ip_idx: The index of the IP wildcard to retrieve. - :type ip_idx: int - :return: The wildcard address. - :rtype: str - """ - if wildcard_idx >= len(self.wildcard_list): - msg = ( - f"Error: agent attempted to perform an action on ip wildcard {wildcard_idx} but this" - f" is out of range for its action space. Wildcard list: {self.wildcard_list}" - ) - _LOGGER.error(msg) - raise RuntimeError(msg) - return self.wildcard_list[wildcard_idx] + # :param ip_idx: The index of the IP wildcard to retrieve. + # :type ip_idx: int + # :return: The wildcard address. + # :rtype: str + # """ + # if wildcard_idx >= len(self.wildcard_list): + # msg = ( + # f"Error: agent attempted to perform an action on ip wildcard {wildcard_idx} but this" + # f" is out of range for its action space. Wildcard list: {self.wildcard_list}" + # ) + # _LOGGER.error(msg) + # raise RuntimeError(msg) + # return self.wildcard_list[wildcard_idx] - def get_port_by_idx(self, port_idx: int) -> str: - """ - Get the port corresponding to the given index. + # def get_port_by_idx(self, port_idx: int) -> str: + # """ + # Get the port corresponding to the given index. - :param port_idx: The index of the port to retrieve. - :type port_idx: int - :return: The port. - :rtype: str - """ - if port_idx >= len(self.ports): - msg = ( - f"Error: agent attempted to perform an action on port {port_idx} but this" - f" is out of range for its action space. Port list: {self.ip_address_list}" - ) - _LOGGER.error(msg) - raise RuntimeError(msg) - return self.ports[port_idx] + # :param port_idx: The index of the port to retrieve. + # :type port_idx: int + # :return: The port. + # :rtype: str + # """ + # if port_idx >= len(self.ports): + # msg = ( + # f"Error: agent attempted to perform an action on port {port_idx} but this" + # f" is out of range for its action space. Port list: {self.ip_address_list}" + # ) + # _LOGGER.error(msg) + # raise RuntimeError(msg) + # return self.ports[port_idx] - def get_nic_num_by_idx(self, node_idx: int, nic_idx: int) -> int: - """ - Get the NIC number corresponding to the given node and NIC indices. + # def get_nic_num_by_idx(self, node_idx: int, nic_idx: int) -> int: + # """ + # Get the NIC number corresponding to the given node and NIC indices. - :param node_idx: The index of the node. - :type node_idx: int - :param nic_idx: The index of the NIC on the node. - :type nic_idx: int - :return: The NIC number. - :rtype: int - """ - return nic_idx + 1 + # :param node_idx: The index of the node. + # :type node_idx: int + # :param nic_idx: The index of the NIC on the node. + # :type nic_idx: int + # :return: The NIC number. + # :rtype: int + # """ + # return nic_idx + 1 @classmethod def from_config(cls, game: "PrimaiteGame", cfg: Dict) -> "ActionManager": diff --git a/src/primaite/game/agent/actions/node.py b/src/primaite/game/agent/actions/node.py new file mode 100644 index 00000000..cbf035a0 --- /dev/null +++ b/src/primaite/game/agent/actions/node.py @@ -0,0 +1,52 @@ +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +from abc import abstractmethod +from typing import ClassVar, Dict + +from primaite.game.agent.actions.manager import AbstractAction +from primaite.interface.request import RequestFormat + + +class NodeAbstractAction(AbstractAction): + """ + Abstract base class for node actions. + + Any action which applies to a node and uses node_name as its only parameter can inherit from this base class. + """ + + class ConfigSchema(AbstractAction.ConfigSchema): + node_name: str + + verb: ClassVar[str] + + @classmethod + def form_request(cls, config: ConfigSchema) -> RequestFormat: + """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" + return ["network", "node", config.node_name, cls.verb] + + +class NodeOSScanAction(NodeAbstractAction, identifier="node_os_scan"): + """Action which scans a node's OS.""" + + class ConfigSchema(NodeAbstractAction.ConfigSchema): + verb: str = "scan" + + +class NodeShutdownAction(NodeAbstractAction, identifier="node_shutdown"): + """Action which shuts down a node.""" + + class ConfigSchema(NodeAbstractAction.ConfigSchema): + verb: str = "shutdown" + + +class NodeStartupAction(NodeAbstractAction, identifier="node_startup"): + """Action which starts up a node.""" + + class ConfigSchema(NodeAbstractAction.ConfigSchema): + verb: str = "startup" + + +class NodeResetAction(NodeAbstractAction, identifier="node_reset"): + """Action which resets a node.""" + + class ConfigSchema(NodeAbstractAction.ConfigSchema): + verb: str = "reset" diff --git a/src/primaite/game/agent/actions/service.py b/src/primaite/game/agent/actions/service.py index 79d70212..97b37bde 100644 --- a/src/primaite/game/agent/actions/service.py +++ b/src/primaite/game/agent/actions/service.py @@ -1,7 +1,10 @@ +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import ClassVar + from primaite.game.agent.actions.manager import AbstractAction from primaite.interface.request import RequestFormat + class NodeServiceAbstractAction(AbstractAction): class ConfigSchema(AbstractAction.ConfigSchema): node_name: str @@ -10,33 +13,69 @@ class NodeServiceAbstractAction(AbstractAction): verb: ClassVar[str] @classmethod - def form_request(cls, config:ConfigSchema) -> RequestFormat: + def form_request(cls, config: ConfigSchema) -> RequestFormat: """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" return ["network", "node", config.node_name, "service", config.service_name, cls.verb] + class NodeServiceScanAction(NodeServiceAbstractAction, identifier="node_service_scan"): - verb: str = "scan" + """Action which scans a service.""" -class NodeServiceStopAction(NodeServiceAbstractAction, identifier=...): - verb: str = "stop" + class ConfigSchema(NodeServiceAbstractAction.ConfigSchema): + verb: str = "scan" -class NodeServiceStartAction(NodeServiceAbstractAction): - verb: str = "start" -class NodeServicePauseAction(NodeServiceAbstractAction): - verb: str = "pause" +class NodeServiceStopAction(NodeServiceAbstractAction, identifier="node_service_stop"): + """Action which stops a service.""" -class NodeServiceResumeAction(NodeServiceAbstractAction): - verb: str = "resume" + class ConfigSchema(NodeServiceAbstractAction.ConfigSchema): + verb: str = "stop" -class NodeServiceRestartAction(NodeServiceAbstractAction): - verb: str = "restart" -class NodeServiceDisableAction(NodeServiceAbstractAction): - verb: str = "disable" +class NodeServiceStartAction(NodeServiceAbstractAction, identifier="node_service_start"): + """Action which starts a service.""" -class NodeServiceEnableAction(NodeServiceAbstractAction): - verb: str = "enable" + class ConfigSchema(NodeServiceAbstractAction.ConfigSchema): + verb: str = "start" -class NodeServiceFixAction(NodeServiceAbstractAction): - verb: str = "fix" + +class NodeServicePauseAction(NodeServiceAbstractAction, identifier="node_service_pause"): + """Action which pauses a service.""" + + class ConfigSchema(NodeServiceAbstractAction.ConfigSchema): + verb: str = "pause" + + +class NodeServiceResumeAction(NodeServiceAbstractAction, identifier="node_service_resume"): + """Action which resumes a service.""" + + class ConfigSchema(NodeServiceAbstractAction.ConfigSchema): + verb: str = "resume" + + +class NodeServiceRestartAction(NodeServiceAbstractAction, identifier="node_service_restart"): + """Action which restarts a service.""" + + class ConfigSchema(NodeServiceAbstractAction.ConfigSchema): + verb: str = "restart" + + +class NodeServiceDisableAction(NodeServiceAbstractAction, identifier="node_service_disable"): + """Action which disables a service.""" + + class ConfigSchema(NodeServiceAbstractAction.ConfigSchema): + verb: str = "disable" + + +class NodeServiceEnableAction(NodeServiceAbstractAction, identifier="node_service_enable"): + """Action which enables a service.""" + + class ConfigSchema(NodeServiceAbstractAction.ConfigSchema): + verb: str = "enable" + + +class NodeServiceFixAction(NodeServiceAbstractAction, identifier="node_service_fix"): + """Action which fixes a service.""" + + class ConfigSchema(NodeServiceAbstractAction.ConfigSchema): + verb: str = "fix" From fe6a8e6e97cb7a3837091950d7038b52fe1ffbff Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Thu, 17 Oct 2024 13:24:57 +0100 Subject: [PATCH 03/58] #2913: Initial commit of new AbstractReward class. --- src/primaite/game/agent/rewards.py | 61 +++++++++++++++++++----------- 1 file changed, 38 insertions(+), 23 deletions(-) diff --git a/src/primaite/game/agent/rewards.py b/src/primaite/game/agent/rewards.py index 1de34b40..0db3cc28 100644 --- a/src/primaite/game/agent/rewards.py +++ b/src/primaite/game/agent/rewards.py @@ -27,9 +27,10 @@ the structure: service_ref: web_server_database_client ``` """ -from abc import abstractmethod -from typing import Callable, Dict, Iterable, List, Optional, Tuple, Type, TYPE_CHECKING, Union +from abc import ABC, abstractmethod +from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Type, TYPE_CHECKING, Union +from pydantic import BaseModel from typing_extensions import Never from primaite import getLogger @@ -42,9 +43,35 @@ _LOGGER = getLogger(__name__) WhereType = Optional[Iterable[Union[str, int]]] -class AbstractReward: +class AbstractReward(BaseModel): """Base class for reward function components.""" + class ConfigSchema(BaseModel, ABC): + """Config schema for AbstractReward.""" + + type: str + + def __init_subclass__(cls, identifier: str, **kwargs: Any) -> None: + super().__init_subclass__(**kwargs) + if identifier in cls._registry: + raise ValueError(f"Duplicate node adder {identifier}") + cls._registry[identifier] = cls + + @classmethod + @abstractmethod + def from_config(cls, config: Dict) -> "AbstractReward": + """Create a reward function component from a config dictionary. + + :param config: dict of options for the reward component's constructor + :type config: dict + :return: The reward component. + :rtype: AbstractReward + """ + if config["type"] not in cls._registry: + raise ValueError(f"Invalid reward type {config['type']}") + + return cls + @abstractmethod def calculate(self, state: Dict, last_action_response: "AgentHistoryItem") -> float: """Calculate the reward for the current state. @@ -58,21 +85,9 @@ class AbstractReward: """ return 0.0 - @classmethod - @abstractmethod - def from_config(cls, config: dict) -> "AbstractReward": - """Create a reward function component from a config dictionary. - :param config: dict of options for the reward component's constructor - :type config: dict - :return: The reward component. - :rtype: AbstractReward - """ - return cls() - - -class DummyReward(AbstractReward): - """Dummy reward function component which always returns 0.""" +class DummyReward(AbstractReward, identifier="DummyReward"): + """Dummy reward function component which always returns 0.0""" def calculate(self, state: Dict, last_action_response: "AgentHistoryItem") -> float: """Calculate the reward for the current state. @@ -98,7 +113,7 @@ class DummyReward(AbstractReward): return cls() -class DatabaseFileIntegrity(AbstractReward): +class DatabaseFileIntegrity(AbstractReward, identifier="DatabaseFileIntegrity"): """Reward function component which rewards the agent for maintaining the integrity of a database file.""" def __init__(self, node_hostname: str, folder_name: str, file_name: str) -> None: @@ -168,7 +183,7 @@ class DatabaseFileIntegrity(AbstractReward): return cls(node_hostname=node_hostname, folder_name=folder_name, file_name=file_name) -class WebServer404Penalty(AbstractReward): +class WebServer404Penalty(AbstractReward, identifier="WebServer404Penalty"): """Reward function component which penalises the agent when the web server returns a 404 error.""" def __init__(self, node_hostname: str, service_name: str, sticky: bool = True) -> None: @@ -241,7 +256,7 @@ class WebServer404Penalty(AbstractReward): return cls(node_hostname=node_hostname, service_name=service_name, sticky=sticky) -class WebpageUnavailablePenalty(AbstractReward): +class WebpageUnavailablePenalty(AbstractReward, identifier="WebpageUnavailablePenalty"): """Penalises the agent when the web browser fails to fetch a webpage.""" def __init__(self, node_hostname: str, sticky: bool = True) -> None: @@ -325,7 +340,7 @@ class WebpageUnavailablePenalty(AbstractReward): return cls(node_hostname=node_hostname, sticky=sticky) -class GreenAdminDatabaseUnreachablePenalty(AbstractReward): +class GreenAdminDatabaseUnreachablePenalty(AbstractReward, identifier="GreenAdminDatabaseUnreachablePenalty"): """Penalises the agent when the green db clients fail to connect to the database.""" def __init__(self, node_hostname: str, sticky: bool = True) -> None: @@ -393,7 +408,7 @@ class GreenAdminDatabaseUnreachablePenalty(AbstractReward): return cls(node_hostname=node_hostname, sticky=sticky) -class SharedReward(AbstractReward): +class SharedReward(AbstractReward, identifier="SharedReward"): """Adds another agent's reward to the overall reward.""" def __init__(self, agent_name: Optional[str] = None) -> None: @@ -446,7 +461,7 @@ class SharedReward(AbstractReward): return cls(agent_name=agent_name) -class ActionPenalty(AbstractReward): +class ActionPenalty(AbstractReward, identifier="ActionPenalty"): """Apply a negative reward when taking any action except DONOTHING.""" def __init__(self, action_penalty: float, do_nothing_penalty: float) -> None: From 419a86114d0b6eca4d2da343c427ddddf1b297cf Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Thu, 17 Oct 2024 16:35:13 +0100 Subject: [PATCH 04/58] #2913: Now with ConfigSchemas. --- src/primaite/game/agent/rewards.py | 39 +++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/src/primaite/game/agent/rewards.py b/src/primaite/game/agent/rewards.py index 0db3cc28..4198af27 100644 --- a/src/primaite/game/agent/rewards.py +++ b/src/primaite/game/agent/rewards.py @@ -87,7 +87,7 @@ class AbstractReward(BaseModel): class DummyReward(AbstractReward, identifier="DummyReward"): - """Dummy reward function component which always returns 0.0""" + """Dummy reward function component which always returns 0.0.""" def calculate(self, state: Dict, last_action_response: "AgentHistoryItem") -> float: """Calculate the reward for the current state. @@ -116,6 +116,13 @@ class DummyReward(AbstractReward, identifier="DummyReward"): class DatabaseFileIntegrity(AbstractReward, identifier="DatabaseFileIntegrity"): """Reward function component which rewards the agent for maintaining the integrity of a database file.""" + class ConfigSchema(AbstractReward.ConfigSchema): + """ConfigSchema for DatabaseFileIntegrity.""" + + node_hostname: str + folder_name: str + file_name: str + def __init__(self, node_hostname: str, folder_name: str, file_name: str) -> None: """Initialise the reward component. @@ -186,6 +193,13 @@ class DatabaseFileIntegrity(AbstractReward, identifier="DatabaseFileIntegrity"): class WebServer404Penalty(AbstractReward, identifier="WebServer404Penalty"): """Reward function component which penalises the agent when the web server returns a 404 error.""" + class ConfigSchema(AbstractReward.ConfigSchema): + """ConfigSchema for WebServer404Penalty.""" + + node_hostname: str + service_name: str + sticky: bool = True + def __init__(self, node_hostname: str, service_name: str, sticky: bool = True) -> None: """Initialise the reward component. @@ -259,6 +273,12 @@ class WebServer404Penalty(AbstractReward, identifier="WebServer404Penalty"): class WebpageUnavailablePenalty(AbstractReward, identifier="WebpageUnavailablePenalty"): """Penalises the agent when the web browser fails to fetch a webpage.""" + class ConfigSchema(AbstractReward.ConfigSchema): + """ConfigSchema for WebpageUnavailablePenalty.""" + + node_hostname: str + sticky: bool = True + def __init__(self, node_hostname: str, sticky: bool = True) -> None: """ Initialise the reward component. @@ -343,6 +363,12 @@ class WebpageUnavailablePenalty(AbstractReward, identifier="WebpageUnavailablePe class GreenAdminDatabaseUnreachablePenalty(AbstractReward, identifier="GreenAdminDatabaseUnreachablePenalty"): """Penalises the agent when the green db clients fail to connect to the database.""" + class ConfigSchema(AbstractReward.ConfigSchema): + """ConfigSchema for GreenAdminDatabaseUnreachablePenalty.""" + + node_hostname: str + sticky: bool = True + def __init__(self, node_hostname: str, sticky: bool = True) -> None: """ Initialise the reward component. @@ -411,6 +437,11 @@ class GreenAdminDatabaseUnreachablePenalty(AbstractReward, identifier="GreenAdmi class SharedReward(AbstractReward, identifier="SharedReward"): """Adds another agent's reward to the overall reward.""" + class ConfigSchema(AbstractReward.ConfigSchema): + """Config schema for SharedReward.""" + + agent_name: str + def __init__(self, agent_name: Optional[str] = None) -> None: """ Initialise the shared reward. @@ -464,6 +495,12 @@ class SharedReward(AbstractReward, identifier="SharedReward"): class ActionPenalty(AbstractReward, identifier="ActionPenalty"): """Apply a negative reward when taking any action except DONOTHING.""" + class ConfigSchema(AbstractReward.ConfigSchema): + """Config schema for ActionPenalty.""" + + action_penalty: float = -1.0 + do_nothing_penalty: float = 0.0 + def __init__(self, action_penalty: float, do_nothing_penalty: float) -> None: """ Initialise the reward. From a90aec2bcd133ffbc2f9f63d028a54e01f382bae Mon Sep 17 00:00:00 2001 From: Charlie Crane Date: Thu, 17 Oct 2024 16:59:44 +0100 Subject: [PATCH 05/58] #2912 - End of day commit --- src/primaite/game/agent/actions/acl.py | 171 ++++++++++++++++++ .../game/agent/actions/application.py | 31 +++- src/primaite/game/agent/actions/config.py | 107 +++++++++++ src/primaite/game/agent/actions/file.py | 18 +- src/primaite/game/agent/actions/folder.py | 12 ++ src/primaite/game/agent/actions/host_nic.py | 57 ++++++ src/primaite/game/agent/actions/manager.py | 5 +- src/primaite/game/agent/actions/network.py | 43 +++++ src/primaite/game/agent/actions/node.py | 10 + 9 files changed, 446 insertions(+), 8 deletions(-) create mode 100644 src/primaite/game/agent/actions/config.py create mode 100644 src/primaite/game/agent/actions/host_nic.py create mode 100644 src/primaite/game/agent/actions/network.py diff --git a/src/primaite/game/agent/actions/acl.py b/src/primaite/game/agent/actions/acl.py index 22e0a465..6aeafe4d 100644 --- a/src/primaite/game/agent/actions/acl.py +++ b/src/primaite/game/agent/actions/acl.py @@ -5,6 +5,7 @@ from pydantic import BaseModel, Field, field_validator, ValidationInfo from primaite.game.agent.actions.manager import AbstractAction from primaite.game.game import _LOGGER +from primaite.interface.request import RequestFormat class RouterACLAddRuleAction(AbstractAction, identifier="router_acl_add_rule"): @@ -168,3 +169,173 @@ class RouterACLAddRuleAction(AbstractAction, identifier="router_acl_add_rule"): dst_port, position, ] + + +class RouterACLRemoveRuleAction(AbstractAction): + """Action which removes a rule from a router's ACL.""" + + class ConfigSchema(AbstractAction.ConfigSchema): + """Configuration schema for RouterACLRemoveRuleAction.""" + + target_router: str + position: str + + @classmethod + def form_request(cls, config: ConfigSchema) -> RequestFormat: + """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" + return ["network", "node", config.target_router, "acl", "remove_rule", config.position] + + +class FirewallACLAddRuleAction(AbstractAction): + """Action which adds a rule to a firewall port's ACL.""" + + def __init__( + self, + manager: "ActionManager", + max_acl_rules: int, + num_ips: int, + num_ports: int, + num_protocols: int, + **kwargs, + ) -> None: + """Init method for FirewallACLAddRuleAction. + + :param manager: Reference to the ActionManager which created this action. + :type manager: ActionManager + :param max_acl_rules: Maximum number of ACL rules that can be added to the router. + :type max_acl_rules: int + :param num_ips: Number of IP addresses in the simulation. + :type num_ips: int + :param num_ports: Number of ports in the simulation. + :type num_ports: int + :param num_protocols: Number of protocols in the simulation. + :type num_protocols: int + """ + super().__init__(manager=manager) + num_permissions = 3 + self.shape: Dict[str, int] = { + "position": max_acl_rules, + "permission": num_permissions, + "source_ip_id": num_ips, + "dest_ip_id": num_ips, + "source_port_id": num_ports, + "dest_port_id": num_ports, + "protocol_id": num_protocols, + } + + def form_request( + self, + target_firewall_nodename: str, + firewall_port_name: str, + firewall_port_direction: str, + position: int, + permission: int, + source_ip_id: int, + source_wildcard_id: int, + dest_ip_id: int, + dest_wildcard_id: int, + source_port_id: int, + dest_port_id: int, + protocol_id: int, + ) -> List[str]: + """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" + if permission == 0: + permission_str = "UNUSED" + return ["do_nothing"] # NOT SUPPORTED, JUST DO NOTHING IF WE COME ACROSS THIS + elif permission == 1: + permission_str = "PERMIT" + elif permission == 2: + permission_str = "DENY" + else: + _LOGGER.warning(f"{self.__class__} received permission {permission}, expected 0 or 1.") + + if protocol_id == 0: + return ["do_nothing"] # NOT SUPPORTED, JUST DO NOTHING IF WE COME ACROSS THIS + + if protocol_id == 1: + protocol = "ALL" + else: + protocol = self.manager.get_internet_protocol_by_idx(protocol_id - 2) + # subtract 2 to account for UNUSED=0 and ALL=1. + + if source_ip_id == 0: + return ["do_nothing"] # invalid formulation + elif source_ip_id == 1: + src_ip = "ALL" + else: + src_ip = self.manager.get_ip_address_by_idx(source_ip_id - 2) + # subtract 2 to account for UNUSED=0, and ALL=1 + + if source_port_id == 0: + return ["do_nothing"] # invalid formulation + elif source_port_id == 1: + src_port = "ALL" + else: + src_port = self.manager.get_port_by_idx(source_port_id - 2) + # subtract 2 to account for UNUSED=0, and ALL=1 + + if dest_ip_id == 0: + return ["do_nothing"] # invalid formulation + elif dest_ip_id == 1: + dst_ip = "ALL" + else: + dst_ip = self.manager.get_ip_address_by_idx(dest_ip_id - 2) + # subtract 2 to account for UNUSED=0, and ALL=1 + + if dest_port_id == 0: + return ["do_nothing"] # invalid formulation + elif dest_port_id == 1: + dst_port = "ALL" + else: + dst_port = self.manager.get_port_by_idx(dest_port_id - 2) + # subtract 2 to account for UNUSED=0, and ALL=1 + src_wildcard = self.manager.get_wildcard_by_idx(source_wildcard_id) + dst_wildcard = self.manager.get_wildcard_by_idx(dest_wildcard_id) + + return [ + "network", + "node", + target_firewall_nodename, + firewall_port_name, + firewall_port_direction, + "acl", + "add_rule", + permission_str, + protocol, + str(src_ip), + src_wildcard, + src_port, + str(dst_ip), + dst_wildcard, + dst_port, + position, + ] + +class FirewallACLRemoveRuleAction(AbstractAction): + """Action which removes a rule from a firewall port's ACL.""" + + def __init__(self, manager: "ActionManager", max_acl_rules: int, **kwargs) -> None: + """Init method for RouterACLRemoveRuleAction. + + :param manager: Reference to the ActionManager which created this action. + :type manager: ActionManager + :param max_acl_rules: Maximum number of ACL rules that can be added to the router. + :type max_acl_rules: int + """ + super().__init__(manager=manager) + self.shape: Dict[str, int] = {"position": max_acl_rules} + + def form_request( + self, target_firewall_nodename: str, firewall_port_name: str, firewall_port_direction: str, position: int + ) -> List[str]: + """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" + return [ + "network", + "node", + target_firewall_nodename, + firewall_port_name, + firewall_port_direction, + "acl", + "remove_rule", + position, + ] \ No newline at end of file diff --git a/src/primaite/game/agent/actions/application.py b/src/primaite/game/agent/actions/application.py index 4b82ffd3..39a7b224 100644 --- a/src/primaite/game/agent/actions/application.py +++ b/src/primaite/game/agent/actions/application.py @@ -1,7 +1,5 @@ # © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK -from abc import abstractmethod -from typing import ClassVar, Dict +from typing import ClassVar from primaite.game.agent.actions.manager import AbstractAction from primaite.interface.request import RequestFormat @@ -16,6 +14,8 @@ class NodeApplicationAbstractAction(AbstractAction): """ class ConfigSchema(AbstractAction.ConfigSchema): + """Base Configuration schema for Node Application actions.""" + node_name: str application_name: str @@ -33,6 +33,8 @@ class NodeApplicationExecuteAction(NodeApplicationAbstractAction, identifier="no """Action which executes an application.""" class ConfigSchema(NodeApplicationAbstractAction.ConfigSchema): + """Configuration schema for NodeApplicationExecuteAction.""" + verb: str = "execute" @@ -40,6 +42,8 @@ class NodeApplicationScanAction(NodeApplicationAbstractAction, identifier="node_ """Action which scans an application.""" class ConfigSchema(NodeApplicationAbstractAction.ConfigSchema): + """Configuration schema for NodeApplicationScanAction.""" + verb: str = "scan" @@ -47,6 +51,8 @@ class NodeApplicationCloseAction(NodeApplicationAbstractAction, identifier="node """Action which closes an application.""" class ConfigSchema(NodeApplicationAbstractAction.ConfigSchema): + """Configuration schema for NodeApplicationCloseAction.""" + verb: str = "close" @@ -54,11 +60,28 @@ class NodeApplicationFixAction(NodeApplicationAbstractAction, identifier="node_a """Action which fixes an application.""" class ConfigSchema(NodeApplicationAbstractAction.ConfigSchema): + """Configuration schema for NodeApplicationFixAction.""" + verb: str = "fix" -class NodeApplicationInstallAction(AbstractAction): +class NodeApplicationInstallAction(NodeApplicationAbstractAction): """Action which installs an application.""" class ConfigSchema(NodeApplicationAbstractAction.ConfigSchema): + """Configuration schema for NodeApplicationInstallAction.""" + verb: str = "install" + + # TODO: Either changes to application form_request bits, or add that here. + +class NodeApplicationRemoveAction(NodeApplicationAbstractAction): + """Action which removes/uninstalls an application""" + + class ConfigSchema(NodeApplicationAbstractAction.ConfigSchema): + """Configuration schema for NodeApplicationRemoveAction.""" + + verb: str = "uninstall" + + # TODO: Either changes to application form_request bits, or add that here. + diff --git a/src/primaite/game/agent/actions/config.py b/src/primaite/game/agent/actions/config.py new file mode 100644 index 00000000..8b3f99f1 --- /dev/null +++ b/src/primaite/game/agent/actions/config.py @@ -0,0 +1,107 @@ +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + +from typing import Dict, Optional +from pydantic import BaseModel, ConfigDict, Field, ValidationInfo, field_validator +from primaite.game.agent.actions.manager import AbstractAction +from primaite.interface.request import RequestFormat + + +class ConfigureRansomwareScriptAction(AbstractAction): + """Action which sets config parameters for a ransomware script on a node.""" + + class _Opts(BaseModel): + """Schema for options that can be passed to this option.""" + + model_config = ConfigDict(extra="forbid") + server_ip_address: Optional[str] = None + server_password: Optional[str] = None + payload: Optional[str] = None + + def __init__(self, manager: "ActionManager", **kwargs) -> None: + super().__init__(manager=manager) + + def form_request(self, node_id: int, config: Dict) -> RequestFormat: + """Return the action formatted as a request that can be ingested by the simulation.""" + node_name = self.manager.get_node_name_by_idx(node_id) + if node_name is None: + return ["do_nothing"] + ConfigureRansomwareScriptAction._Opts.model_validate(config) # check that options adhere to schema + return ["network", "node", node_name, "application", "RansomwareScript", "configure", config] + +class ConfigureDoSBotAction(AbstractAction): + """Action which sets config parameters for a DoS bot on a node.""" + + class _Opts(BaseModel): + """Schema for options that can be passed to this action.""" + + model_config = ConfigDict(extra="forbid") + target_ip_address: Optional[str] = None + target_port: Optional[str] = None + payload: Optional[str] = None + repeat: Optional[bool] = None + port_scan_p_of_success: Optional[float] = None + dos_intensity: Optional[float] = None + max_sessions: Optional[int] = None + + def __init__(self, manager: "ActionManager", **kwargs) -> None: + super().__init__(manager=manager) + + def form_request(self, node_id: int, config: Dict) -> RequestFormat: + """Return the action formatted as a request that can be ingested by the simulation.""" + node_name = self.manager.get_node_name_by_idx(node_id) + if node_name is None: + return ["do_nothing"] + self._Opts.model_validate(config) # check that options adhere to schema + return ["network", "node", node_name, "application", "DoSBot", "configure", config] + + +class ConfigureC2BeaconAction(AbstractAction): + """Action which configures a C2 Beacon based on the parameters given.""" + + class ConfigSchema(AbstractAction.ConfigSchema): + """Configuration schema for ConfigureC2BeaconAction.""" + + node_name: str + c2_server_ip_address: str + keep_alive_frequency: int = Field(default=5, ge=1) + masquerade_protocol: str = Field(default="TCP") + masquerade_port: str = Field(default="HTTP") + + + class _Opts(BaseModel): + """Schema for options that can be passed to this action.""" + + c2_server_ip_address: str + keep_alive_frequency: int = Field(default=5, ge=1) + masquerade_protocol: str = Field(default="TCP") + masquerade_port: str = Field(default="HTTP") + + @field_validator( + "c2_server_ip_address", + "keep_alive_frequency", + "masquerade_protocol", + "masquerade_port", + mode="before", + ) + @classmethod + def not_none(cls, v: str, info: ValidationInfo) -> int: + """If None is passed, use the default value instead.""" + if v is None: + return cls.model_fields[info.field_name].default + return v + + @classmethod + def form_request(self, config: ConfigSchema) -> RequestFormat: + """Return the action formatted as a request that can be ingested by the simulation.""" + if config.node_name is None: + return ["do_nothing"] + config = ConfigureC2BeaconAction._Opts( + c2_server_ip_address=config["c2_server_ip_address"], + keep_alive_frequency=config["keep_alive_frequency"], + masquerade_port=config["masquerade_port"], + masquerade_protocol=config["masquerade_protocol"], + ) + + ConfigureC2BeaconAction._Opts.model_validate(config) # check that options adhere to schema + + return ["network", "node", config.node_name, "application", "C2Beacon", "configure", config.__dict__] \ No newline at end of file diff --git a/src/primaite/game/agent/actions/file.py b/src/primaite/game/agent/actions/file.py index d21daa9b..77bd8ef3 100644 --- a/src/primaite/game/agent/actions/file.py +++ b/src/primaite/game/agent/actions/file.py @@ -8,11 +8,13 @@ from primaite.interface.request import RequestFormat class NodeFileAbstractAction(AbstractAction): """Abstract base class for file actions. - Any action which applies to a file and uses node_name, folder_name, and file_name as its only three parameters can inherit - from this base class. + Any action which applies to a file and uses node_name, folder_name, and file_name as its + only three parameters can inherit from this base class. """ class ConfigSchema(AbstractAction.ConfigSchema): + """Configuration Schema for NodeFileAbstractAction.""" + node_name: str folder_name: str file_name: str @@ -41,6 +43,8 @@ class NodeFileCreateAction(NodeFileAbstractAction, identifier="node_file_create" """Action which creates a new file in a given folder.""" class ConfigSchema(NodeFileAbstractAction.ConfigSchema): + """Configuration schema for NodeFileCreateAction.""" + verb: str = "create" @@ -48,6 +52,8 @@ class NodeFileScanAction(NodeFileAbstractAction, identifier="node_file_scan"): """Action which scans a file.""" class ConfigSchema(NodeFileAbstractAction.ConfigSchema): + """Configuration schema for NodeFileScanAction.""" + verb: str = "scan" @@ -55,6 +61,8 @@ class NodeFileDeleteAction(NodeFileAbstractAction, identifier="node_file_delete" """Action which deletes a file.""" class ConfigSchema(NodeFileAbstractAction.ConfigSchema): + """Configuration schema for NodeFileDeleteAction.""" + verb: str = "delete" @@ -62,6 +70,8 @@ class NodeFileRestoreAction(NodeFileAbstractAction, identifier="node_file_restor """Action which restores a file.""" class ConfigSchema(NodeFileAbstractAction.ConfigSchema): + """Configuration schema for NodeFileRestoreAction.""" + verb: str = "restore" @@ -69,6 +79,8 @@ class NodeFileCorruptAction(NodeFileAbstractAction, identifier="node_file_corrup """Action which corrupts a file.""" class ConfigSchema(NodeFileAbstractAction.ConfigSchema): + """Configuration schema for NodeFileCorruptAction.""" + verb: str = "corrupt" @@ -76,4 +88,6 @@ class NodeFileAccessAction(NodeFileAbstractAction, identifier="node_file_access" """Action which increases a file's access count.""" class ConfigSchema(NodeFileAbstractAction.ConfigSchema): + """Configuration schema for NodeFileAccessAction.""" + verb: str = "access" diff --git a/src/primaite/game/agent/actions/folder.py b/src/primaite/game/agent/actions/folder.py index 278f5658..255731f6 100644 --- a/src/primaite/game/agent/actions/folder.py +++ b/src/primaite/game/agent/actions/folder.py @@ -15,6 +15,8 @@ class NodeFolderAbstractAction(AbstractAction): """ class ConfigSchema(AbstractAction.ConfigSchema): + """Base configuration schema for NodeFolder actions.""" + node_name: str folder_name: str @@ -34,6 +36,8 @@ class NodeFolderScanAction(NodeFolderAbstractAction, identifier="node_folder_sca """Action which scans a folder.""" class ConfigSchema(NodeFolderAbstractAction.ConfigSchema): + """Configuration schema for NodeFolderScanAction.""" + verb: str = "scan" @@ -41,6 +45,8 @@ class NodeFolderCheckhashAction(NodeFolderAbstractAction, identifier="node_folde """Action which checks the hash of a folder.""" class ConfigSchema(NodeFolderAbstractAction.ConfigSchema): + """Configuration schema for NodeFolderCheckhashAction.""" + verb: str = "checkhash" @@ -48,6 +54,8 @@ class NodeFolderRepairAction(NodeFolderAbstractAction, identifier="node_folder_r """Action which repairs a folder.""" class ConfigSchema(NodeFolderAbstractAction.ConfigSchema): + """Configuration schema for NodeFolderRepairAction.""" + verb: str = "repair" @@ -55,6 +63,8 @@ class NodeFolderRestoreAction(NodeFolderAbstractAction, identifier="node_folder_ """Action which restores a folder.""" class ConfigSchema(NodeFolderAbstractAction.ConfigSchema): + """Configuration schema for NodeFolderRestoreAction.""" + verb: str = "restore" @@ -62,4 +72,6 @@ class NodeFolderCreateAction(AbstractAction, identifier="node_folder_create"): """Action which creates a new folder.""" class ConfigSchema(NodeFolderAbstractAction.ConfigSchema): + """Configuration schema for NodeFolderCreateAction.""" + verb: str = "create" diff --git a/src/primaite/game/agent/actions/host_nic.py b/src/primaite/game/agent/actions/host_nic.py new file mode 100644 index 00000000..f8be9465 --- /dev/null +++ b/src/primaite/game/agent/actions/host_nic.py @@ -0,0 +1,57 @@ +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + +from typing import Dict, Optional +from pydantic import BaseModel, ConfigDict +from primaite.game.agent.actions.manager import AbstractAction +from primaite.interface.request import RequestFormat + +class HostNICAbstractAction(AbstractAction): + """ + Abstract base class for NIC actions. + + Any action which applies to a NIC and uses node_id and nic_id as its only two parameters can inherit from this base + class. + """ + + class ConfigSchema(AbstractAction.ConfigSchema): + """Base Configuration schema for HostNIC actions.""" + num_nodes: str + max_nics_per_node: str + node_name: str + nic_num: str + + def __init__(self, manager: "ActionManager", num_nodes: int, max_nics_per_node: int, **kwargs) -> None: + """Init method for HostNICAbstractAction. + + :param manager: Reference to the ActionManager which created this action. + :type manager: ActionManager + :param num_nodes: Number of nodes in the simulation. + :type num_nodes: int + :param max_nics_per_node: Maximum number of NICs per node. + :type max_nics_per_node: int + """ + super().__init__(manager=manager) + self.shape: Dict[str, int] = {"node_id": num_nodes, "nic_id": max_nics_per_node} + self.verb: str # define but don't initialise: defends against children classes not defining this + + @classmethod + def form_request(cls, config: ConfigSchema) -> RequestFormat: + """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" + if config.node_name is None or config.nic_num is None: + return ["do_nothing"] + return ["network", "node", config.node_name, "network_interface", config.nic_num, cls.verb] + +class HostNICEnableAction(HostNICAbstractAction): + """Action which enables a NIC.""" + + class ConfigSchema(HostNICAbstractAction.ConfigSchema): + """Configuration schema for HostNICEnableAction.""" + verb: str = "enable" + + +class HostNICDisableAction(HostNICAbstractAction): + """Action which disables a NIC.""" + + class ConfigSchema(HostNICAbstractAction.ConfigSchema): + """Configuration schema for HostNICDisableAction.""" + verb: str = "disable" \ No newline at end of file diff --git a/src/primaite/game/agent/actions/manager.py b/src/primaite/game/agent/actions/manager.py index 99ce091e..09e5a851 100644 --- a/src/primaite/game/agent/actions/manager.py +++ b/src/primaite/game/agent/actions/manager.py @@ -14,13 +14,13 @@ agents: from __future__ import annotations import itertools -from abc import ABC, abstractmethod +from abc import ABC from typing import Any, ClassVar, Dict, List, Literal, Optional, Tuple, Type from gymnasium import spaces from pydantic import BaseModel, ConfigDict -from primaite.game.game import _LOGGER, PrimaiteGame +from primaite.game.game import PrimaiteGame from primaite.interface.request import RequestFormat @@ -57,6 +57,7 @@ class DoNothingAction(AbstractAction): type: Literal["do_nothing"] = "do_nothing" def form_request(self, options: ConfigSchema) -> RequestFormat: + """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" return ["do_nothing"] diff --git a/src/primaite/game/agent/actions/network.py b/src/primaite/game/agent/actions/network.py new file mode 100644 index 00000000..aa6ef4d3 --- /dev/null +++ b/src/primaite/game/agent/actions/network.py @@ -0,0 +1,43 @@ +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + +from typing import Dict, Optional +from pydantic import BaseModel, ConfigDict +from primaite.game.agent.actions.manager import AbstractAction +from primaite.interface.request import RequestFormat + +class NetworkPortEnableAction(AbstractAction): + """Action which enables are port on a router or a firewall.""" + + def __init__(self, manager: "ActionManager", max_nics_per_node: int, **kwargs) -> None: + """Init method for NetworkPortEnableAction. + + :param max_nics_per_node: Maximum number of NICs per node. + :type max_nics_per_node: int + """ + super().__init__(manager=manager) + self.shape: Dict[str, int] = {"port_id": max_nics_per_node} + + def form_request(self, target_nodename: str, port_id: int) -> RequestFormat: + """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" + if target_nodename is None or port_id is None: + return ["do_nothing"] + return ["network", "node", target_nodename, "network_interface", port_id, "enable"] + + +class NetworkPortDisableAction(AbstractAction): + """Action which disables are port on a router or a firewall.""" + + def __init__(self, manager: "ActionManager", max_nics_per_node: int, **kwargs) -> None: + """Init method for NetworkPortDisableAction. + + :param max_nics_per_node: Maximum number of NICs per node. + :type max_nics_per_node: int + """ + super().__init__(manager=manager) + self.shape: Dict[str, int] = {"port_id": max_nics_per_node} + + def form_request(self, target_nodename: str, port_id: int) -> RequestFormat: + """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" + if target_nodename is None or port_id is None: + return ["do_nothing"] + return ["network", "node", target_nodename, "network_interface", port_id, "disable"] \ No newline at end of file diff --git a/src/primaite/game/agent/actions/node.py b/src/primaite/game/agent/actions/node.py index cbf035a0..d431d344 100644 --- a/src/primaite/game/agent/actions/node.py +++ b/src/primaite/game/agent/actions/node.py @@ -14,6 +14,8 @@ class NodeAbstractAction(AbstractAction): """ class ConfigSchema(AbstractAction.ConfigSchema): + """Base Configuration schema for Node actions.""" + node_name: str verb: ClassVar[str] @@ -28,6 +30,8 @@ class NodeOSScanAction(NodeAbstractAction, identifier="node_os_scan"): """Action which scans a node's OS.""" class ConfigSchema(NodeAbstractAction.ConfigSchema): + """Configuration schema for NodeOSScanAction.""" + verb: str = "scan" @@ -35,6 +39,8 @@ class NodeShutdownAction(NodeAbstractAction, identifier="node_shutdown"): """Action which shuts down a node.""" class ConfigSchema(NodeAbstractAction.ConfigSchema): + """Configuration schema for NodeShutdownAction.""" + verb: str = "shutdown" @@ -42,6 +48,8 @@ class NodeStartupAction(NodeAbstractAction, identifier="node_startup"): """Action which starts up a node.""" class ConfigSchema(NodeAbstractAction.ConfigSchema): + """Configuration schema for NodeStartupAction.""" + verb: str = "startup" @@ -49,4 +57,6 @@ class NodeResetAction(NodeAbstractAction, identifier="node_reset"): """Action which resets a node.""" class ConfigSchema(NodeAbstractAction.ConfigSchema): + """Configuration schema for NodeResetAction.""" + verb: str = "reset" From 1b1f3e4f714db8801369fc52985e1c5b4e1159a2 Mon Sep 17 00:00:00 2001 From: Charlie Crane Date: Fri, 18 Oct 2024 12:07:53 +0100 Subject: [PATCH 06/58] #2912 - Updates to remaining action refactoring --- src/primaite/game/agent/actions/__init__.py | 13 +++++++++++ src/primaite/game/agent/actions/acl.py | 14 ++++++----- .../game/agent/actions/application.py | 2 +- src/primaite/game/agent/actions/config.py | 10 ++++---- src/primaite/game/agent/actions/folder.py | 8 +++---- src/primaite/game/agent/actions/host_nic.py | 23 +++++++------------ src/primaite/game/agent/actions/manager.py | 2 ++ src/primaite/game/agent/actions/network.py | 5 +++- 8 files changed, 45 insertions(+), 32 deletions(-) diff --git a/src/primaite/game/agent/actions/__init__.py b/src/primaite/game/agent/actions/__init__.py index 24a3ad67..9d6c0fcc 100644 --- a/src/primaite/game/agent/actions/__init__.py +++ b/src/primaite/game/agent/actions/__init__.py @@ -25,3 +25,16 @@ __all__ = ( "NodeServiceStopAction", "ActionManager", ) + +# __all__ = ( +# "acl", +# "application", +# "config", +# "file", +# "folder", +# "host_nic", +# "manager", +# "network", +# "node", +# "service", +# ) diff --git a/src/primaite/game/agent/actions/acl.py b/src/primaite/game/agent/actions/acl.py index 6aeafe4d..050e94e8 100644 --- a/src/primaite/game/agent/actions/acl.py +++ b/src/primaite/game/agent/actions/acl.py @@ -171,7 +171,7 @@ class RouterACLAddRuleAction(AbstractAction, identifier="router_acl_add_rule"): ] -class RouterACLRemoveRuleAction(AbstractAction): +class RouterACLRemoveRuleAction(AbstractAction, identifier="router_acl_remove_rule"): """Action which removes a rule from a router's ACL.""" class ConfigSchema(AbstractAction.ConfigSchema): @@ -186,7 +186,7 @@ class RouterACLRemoveRuleAction(AbstractAction): return ["network", "node", config.target_router, "acl", "remove_rule", config.position] -class FirewallACLAddRuleAction(AbstractAction): +class FirewallACLAddRuleAction(AbstractAction, identifier="firewall_acl_add_rule"): """Action which adds a rule to a firewall port's ACL.""" def __init__( @@ -310,8 +310,9 @@ class FirewallACLAddRuleAction(AbstractAction): dst_port, position, ] - -class FirewallACLRemoveRuleAction(AbstractAction): + + +class FirewallACLRemoveRuleAction(AbstractAction, identifier="firewall_acl_remove_rule"): """Action which removes a rule from a firewall port's ACL.""" def __init__(self, manager: "ActionManager", max_acl_rules: int, **kwargs) -> None: @@ -325,8 +326,9 @@ class FirewallACLRemoveRuleAction(AbstractAction): super().__init__(manager=manager) self.shape: Dict[str, int] = {"position": max_acl_rules} + @classmethod def form_request( - self, target_firewall_nodename: str, firewall_port_name: str, firewall_port_direction: str, position: int + cls, target_firewall_nodename: str, firewall_port_name: str, firewall_port_direction: str, position: int ) -> List[str]: """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" return [ @@ -338,4 +340,4 @@ class FirewallACLRemoveRuleAction(AbstractAction): "acl", "remove_rule", position, - ] \ No newline at end of file + ] diff --git a/src/primaite/game/agent/actions/application.py b/src/primaite/game/agent/actions/application.py index 39a7b224..110b71da 100644 --- a/src/primaite/game/agent/actions/application.py +++ b/src/primaite/game/agent/actions/application.py @@ -75,6 +75,7 @@ class NodeApplicationInstallAction(NodeApplicationAbstractAction): # TODO: Either changes to application form_request bits, or add that here. + class NodeApplicationRemoveAction(NodeApplicationAbstractAction): """Action which removes/uninstalls an application""" @@ -84,4 +85,3 @@ class NodeApplicationRemoveAction(NodeApplicationAbstractAction): verb: str = "uninstall" # TODO: Either changes to application form_request bits, or add that here. - diff --git a/src/primaite/game/agent/actions/config.py b/src/primaite/game/agent/actions/config.py index 8b3f99f1..c06ce9c8 100644 --- a/src/primaite/game/agent/actions/config.py +++ b/src/primaite/game/agent/actions/config.py @@ -1,7 +1,9 @@ # © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import Dict, Optional -from pydantic import BaseModel, ConfigDict, Field, ValidationInfo, field_validator + +from pydantic import BaseModel, ConfigDict, Field, field_validator, ValidationInfo + from primaite.game.agent.actions.manager import AbstractAction from primaite.interface.request import RequestFormat @@ -28,6 +30,7 @@ class ConfigureRansomwareScriptAction(AbstractAction): ConfigureRansomwareScriptAction._Opts.model_validate(config) # check that options adhere to schema return ["network", "node", node_name, "application", "RansomwareScript", "configure", config] + class ConfigureDoSBotAction(AbstractAction): """Action which sets config parameters for a DoS bot on a node.""" @@ -53,7 +56,7 @@ class ConfigureDoSBotAction(AbstractAction): return ["do_nothing"] self._Opts.model_validate(config) # check that options adhere to schema return ["network", "node", node_name, "application", "DoSBot", "configure", config] - + class ConfigureC2BeaconAction(AbstractAction): """Action which configures a C2 Beacon based on the parameters given.""" @@ -67,7 +70,6 @@ class ConfigureC2BeaconAction(AbstractAction): masquerade_protocol: str = Field(default="TCP") masquerade_port: str = Field(default="HTTP") - class _Opts(BaseModel): """Schema for options that can be passed to this action.""" @@ -104,4 +106,4 @@ class ConfigureC2BeaconAction(AbstractAction): ConfigureC2BeaconAction._Opts.model_validate(config) # check that options adhere to schema - return ["network", "node", config.node_name, "application", "C2Beacon", "configure", config.__dict__] \ No newline at end of file + return ["network", "node", config.node_name, "application", "C2Beacon", "configure", config.__dict__] diff --git a/src/primaite/game/agent/actions/folder.py b/src/primaite/game/agent/actions/folder.py index 255731f6..b9e003c7 100644 --- a/src/primaite/game/agent/actions/folder.py +++ b/src/primaite/game/agent/actions/folder.py @@ -23,13 +23,11 @@ class NodeFolderAbstractAction(AbstractAction): verb: ClassVar[str] @classmethod - def form_request(cls, node_id: int, folder_id: int) -> RequestFormat: + def form_request(cls, config: ConfigSchema) -> RequestFormat: """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - node_name = cls.manager.get_node_name_by_idx(node_id) - folder_name = cls.manager.get_folder_name_by_idx(node_idx=node_id, folder_idx=folder_id) - if node_name is None or folder_name is None: + if config.node_name is None or config.folder_name is None: return ["do_nothing"] - return ["network", "node", node_name, "file_system", "folder", folder_name, cls.verb] + return ["network", "node", config.node_name, "file_system", "folder", config.folder_name, cls.verb] class NodeFolderScanAction(NodeFolderAbstractAction, identifier="node_folder_scan"): diff --git a/src/primaite/game/agent/actions/host_nic.py b/src/primaite/game/agent/actions/host_nic.py index f8be9465..2909772b 100644 --- a/src/primaite/game/agent/actions/host_nic.py +++ b/src/primaite/game/agent/actions/host_nic.py @@ -1,10 +1,13 @@ # © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import Dict, Optional + from pydantic import BaseModel, ConfigDict + from primaite.game.agent.actions.manager import AbstractAction from primaite.interface.request import RequestFormat + class HostNICAbstractAction(AbstractAction): """ Abstract base class for NIC actions. @@ -15,25 +18,12 @@ class HostNICAbstractAction(AbstractAction): class ConfigSchema(AbstractAction.ConfigSchema): """Base Configuration schema for HostNIC actions.""" + num_nodes: str max_nics_per_node: str node_name: str nic_num: str - def __init__(self, manager: "ActionManager", num_nodes: int, max_nics_per_node: int, **kwargs) -> None: - """Init method for HostNICAbstractAction. - - :param manager: Reference to the ActionManager which created this action. - :type manager: ActionManager - :param num_nodes: Number of nodes in the simulation. - :type num_nodes: int - :param max_nics_per_node: Maximum number of NICs per node. - :type max_nics_per_node: int - """ - super().__init__(manager=manager) - self.shape: Dict[str, int] = {"node_id": num_nodes, "nic_id": max_nics_per_node} - self.verb: str # define but don't initialise: defends against children classes not defining this - @classmethod def form_request(cls, config: ConfigSchema) -> RequestFormat: """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" @@ -41,11 +31,13 @@ class HostNICAbstractAction(AbstractAction): return ["do_nothing"] return ["network", "node", config.node_name, "network_interface", config.nic_num, cls.verb] + class HostNICEnableAction(HostNICAbstractAction): """Action which enables a NIC.""" class ConfigSchema(HostNICAbstractAction.ConfigSchema): """Configuration schema for HostNICEnableAction.""" + verb: str = "enable" @@ -54,4 +46,5 @@ class HostNICDisableAction(HostNICAbstractAction): class ConfigSchema(HostNICAbstractAction.ConfigSchema): """Configuration schema for HostNICDisableAction.""" - verb: str = "disable" \ No newline at end of file + + verb: str = "disable" diff --git a/src/primaite/game/agent/actions/manager.py b/src/primaite/game/agent/actions/manager.py index 09e5a851..9621b7f0 100644 --- a/src/primaite/game/agent/actions/manager.py +++ b/src/primaite/game/agent/actions/manager.py @@ -23,6 +23,8 @@ from pydantic import BaseModel, ConfigDict from primaite.game.game import PrimaiteGame from primaite.interface.request import RequestFormat +# TODO: Make sure that actions are backwards compatible where the old YAML format is used. + class AbstractAction(BaseModel): """Base class for actions.""" diff --git a/src/primaite/game/agent/actions/network.py b/src/primaite/game/agent/actions/network.py index aa6ef4d3..e761fb1e 100644 --- a/src/primaite/game/agent/actions/network.py +++ b/src/primaite/game/agent/actions/network.py @@ -1,10 +1,13 @@ # © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import Dict, Optional + from pydantic import BaseModel, ConfigDict + from primaite.game.agent.actions.manager import AbstractAction from primaite.interface.request import RequestFormat + class NetworkPortEnableAction(AbstractAction): """Action which enables are port on a router or a firewall.""" @@ -40,4 +43,4 @@ class NetworkPortDisableAction(AbstractAction): """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" if target_nodename is None or port_id is None: return ["do_nothing"] - return ["network", "node", target_nodename, "network_interface", port_id, "disable"] \ No newline at end of file + return ["network", "node", target_nodename, "network_interface", port_id, "disable"] From 83d3120b0456c11ba87d5789f1737deb961725d9 Mon Sep 17 00:00:00 2001 From: Charlie Crane Date: Fri, 18 Oct 2024 14:52:50 +0100 Subject: [PATCH 07/58] #2912 - Additional actions added to config.py, refactor of HostNIC --- src/primaite/game/agent/actions/config.py | 70 ++++++++++++++++++++- src/primaite/game/agent/actions/host_nic.py | 4 +- src/primaite/game/agent/actions/network.py | 56 ++++++++--------- 3 files changed, 98 insertions(+), 32 deletions(-) diff --git a/src/primaite/game/agent/actions/config.py b/src/primaite/game/agent/actions/config.py index c06ce9c8..6096a0b2 100644 --- a/src/primaite/game/agent/actions/config.py +++ b/src/primaite/game/agent/actions/config.py @@ -1,6 +1,6 @@ # © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK -from typing import Dict, Optional +from typing import Dict, List, Optional, Union from pydantic import BaseModel, ConfigDict, Field, field_validator, ValidationInfo @@ -107,3 +107,71 @@ class ConfigureC2BeaconAction(AbstractAction): ConfigureC2BeaconAction._Opts.model_validate(config) # check that options adhere to schema return ["network", "node", config.node_name, "application", "C2Beacon", "configure", config.__dict__] + + +class NodeSendRemoteCommandAction(AbstractAction): + """Action which sends a terminal command to a remote node via SSH.""" + + def __init__(self, manager: "ActionManager", **kwargs) -> None: + super().__init__(manager=manager) + + def form_request(self, node_id: int, remote_ip: str, command: RequestFormat) -> RequestFormat: + """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" + node_name = self.manager.get_node_name_by_idx(node_id) + return [ + "network", + "node", + node_name, + "service", + "Terminal", + "send_remote_command", + remote_ip, + {"command": command}, + ] + + +class TerminalC2ServerAction(AbstractAction): + """Action which causes the C2 Server to send a command to the C2 Beacon to execute the terminal command passed.""" + + class _Opts(BaseModel): + """Schema for options that can be passed to this action.""" + + commands: Union[List[RequestFormat], RequestFormat] + ip_address: Optional[str] + username: Optional[str] + password: Optional[str] + + def __init__(self, manager: "ActionManager", **kwargs) -> None: + super().__init__(manager=manager) + + def form_request(self, node_id: int, commands: List, ip_address: Optional[str], account: dict) -> RequestFormat: + """Return the action formatted as a request that can be ingested by the simulation.""" + node_name = self.manager.get_node_name_by_idx(node_id) + if node_name is None: + return ["do_nothing"] + + command_model = { + "commands": commands, + "ip_address": ip_address, + "username": account["username"], + "password": account["password"], + } + + TerminalC2ServerAction._Opts.model_validate(command_model) + return ["network", "node", node_name, "application", "C2Server", "terminal_command", command_model] + + +class RansomwareLaunchC2ServerAction(AbstractAction): + """Action which causes the C2 Server to send a command to the C2 Beacon to launch the RansomwareScript.""" + + class ConfigSchema(AbstractAction): + """Configuration schema for RansomwareLaunchC2ServerAction.""" + node_name: str + + @classmethod + def form_request(cls, config: ConfigSchema) -> RequestFormat: + """Return the action formatted as a request that can be ingested by the simulation.""" + if config.node_name is None: + return ["do_nothing"] + # This action currently doesn't require any further configuration options. + return ["network", "node", config.node_name, "application", "C2Server", "ransomware_launch"] \ No newline at end of file diff --git a/src/primaite/game/agent/actions/host_nic.py b/src/primaite/game/agent/actions/host_nic.py index 2909772b..a4dd8d9c 100644 --- a/src/primaite/game/agent/actions/host_nic.py +++ b/src/primaite/game/agent/actions/host_nic.py @@ -32,7 +32,7 @@ class HostNICAbstractAction(AbstractAction): return ["network", "node", config.node_name, "network_interface", config.nic_num, cls.verb] -class HostNICEnableAction(HostNICAbstractAction): +class HostNICEnableAction(HostNICAbstractAction, identifier="host_nic_enable"): """Action which enables a NIC.""" class ConfigSchema(HostNICAbstractAction.ConfigSchema): @@ -41,7 +41,7 @@ class HostNICEnableAction(HostNICAbstractAction): verb: str = "enable" -class HostNICDisableAction(HostNICAbstractAction): +class HostNICDisableAction(HostNICAbstractAction, identifier="host_nic_disable"): """Action which disables a NIC.""" class ConfigSchema(HostNICAbstractAction.ConfigSchema): diff --git a/src/primaite/game/agent/actions/network.py b/src/primaite/game/agent/actions/network.py index e761fb1e..630385bd 100644 --- a/src/primaite/game/agent/actions/network.py +++ b/src/primaite/game/agent/actions/network.py @@ -1,6 +1,6 @@ # © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK -from typing import Dict, Optional +from typing import ClassVar, Dict, Optional from pydantic import BaseModel, ConfigDict @@ -8,39 +8,37 @@ from primaite.game.agent.actions.manager import AbstractAction from primaite.interface.request import RequestFormat -class NetworkPortEnableAction(AbstractAction): +class NetworkPortAbstractAction(AbstractAction): + """Base class for Network port actions""" + + class ConfigSchema(AbstractAction.ConfigSchema): + """Base configuration schema for NetworkPort actions.""" + target_nodename: str + port_id: str + + verb: ClassVar[str] + + @classmethod + def form_request(cls, config: ConfigSchema) -> RequestFormat: + """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" + if config.target_nodename is None or config.port_id is None: + return ["do_nothing"] + return ["network", "node", config.target_nodename, "network_interface", config.port_id, cls.verb] + + +class NetworkPortEnableAction(NetworkPortAbstractAction, identifier="network_port_enable"): """Action which enables are port on a router or a firewall.""" - def __init__(self, manager: "ActionManager", max_nics_per_node: int, **kwargs) -> None: - """Init method for NetworkPortEnableAction. + class ConfigSchema(AbstractAction.ConfigSchema): + """Configuration schema for NetworkPortEnableAction.""" - :param max_nics_per_node: Maximum number of NICs per node. - :type max_nics_per_node: int - """ - super().__init__(manager=manager) - self.shape: Dict[str, int] = {"port_id": max_nics_per_node} + verb: str = "enable" - def form_request(self, target_nodename: str, port_id: int) -> RequestFormat: - """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - if target_nodename is None or port_id is None: - return ["do_nothing"] - return ["network", "node", target_nodename, "network_interface", port_id, "enable"] - - -class NetworkPortDisableAction(AbstractAction): +class NetworkPortDisableAction(NetworkPortAbstractAction, identifier="network_port_disable"): """Action which disables are port on a router or a firewall.""" - def __init__(self, manager: "ActionManager", max_nics_per_node: int, **kwargs) -> None: - """Init method for NetworkPortDisableAction. + class ConfigSchema(AbstractAction.ConfigSchema): + """Configuration schema for NetworkPortDisableAction""" - :param max_nics_per_node: Maximum number of NICs per node. - :type max_nics_per_node: int - """ - super().__init__(manager=manager) - self.shape: Dict[str, int] = {"port_id": max_nics_per_node} + verb: str = "disable" - def form_request(self, target_nodename: str, port_id: int) -> RequestFormat: - """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - if target_nodename is None or port_id is None: - return ["do_nothing"] - return ["network", "node", target_nodename, "network_interface", port_id, "disable"] From a5c7565f0ebb2d121392b5afd79be7df10255e97 Mon Sep 17 00:00:00 2001 From: Charlie Crane Date: Fri, 18 Oct 2024 16:28:15 +0100 Subject: [PATCH 08/58] #2912 - eod commit. Gutted ActionManager and corrected some identifiers. --- src/primaite/game/agent/actions/config.py | 43 ++++- src/primaite/game/agent/actions/host_nic.py | 5 - src/primaite/game/agent/actions/manager.py | 186 -------------------- src/primaite/game/agent/actions/session.py | 64 +++++++ 4 files changed, 106 insertions(+), 192 deletions(-) create mode 100644 src/primaite/game/agent/actions/session.py diff --git a/src/primaite/game/agent/actions/config.py b/src/primaite/game/agent/actions/config.py index 6096a0b2..d627e4b0 100644 --- a/src/primaite/game/agent/actions/config.py +++ b/src/primaite/game/agent/actions/config.py @@ -174,4 +174,45 @@ class RansomwareLaunchC2ServerAction(AbstractAction): if config.node_name is None: return ["do_nothing"] # This action currently doesn't require any further configuration options. - return ["network", "node", config.node_name, "application", "C2Server", "ransomware_launch"] \ No newline at end of file + return ["network", "node", config.node_name, "application", "C2Server", "ransomware_launch"] + +class ExfiltrationC2ServerAction(AbstractAction): + """Action which exfiltrates a target file from a certain node onto the C2 beacon and then the C2 Server.""" + + class _Opts(BaseModel): + """Schema for options that can be passed to this action.""" + + username: Optional[str] + password: Optional[str] + target_ip_address: str + target_file_name: str + target_folder_name: str + exfiltration_folder_name: Optional[str] + + def __init__(self, manager: "ActionManager", **kwargs) -> None: + super().__init__(manager=manager) + + def form_request( + self, + node_id: int, + account: dict, + target_ip_address: str, + target_file_name: str, + target_folder_name: str, + exfiltration_folder_name: Optional[str], + ) -> RequestFormat: + """Return the action formatted as a request that can be ingested by the simulation.""" + node_name = self.manager.get_node_name_by_idx(node_id) + if node_name is None: + return ["do_nothing"] + + command_model = { + "target_file_name": target_file_name, + "target_folder_name": target_folder_name, + "exfiltration_folder_name": exfiltration_folder_name, + "target_ip_address": target_ip_address, + "username": account["username"], + "password": account["password"], + } + ExfiltrationC2ServerAction._Opts.model_validate(command_model) + return ["network", "node", node_name, "application", "C2Server", "exfiltrate", command_model] \ No newline at end of file diff --git a/src/primaite/game/agent/actions/host_nic.py b/src/primaite/game/agent/actions/host_nic.py index a4dd8d9c..2e53cf72 100644 --- a/src/primaite/game/agent/actions/host_nic.py +++ b/src/primaite/game/agent/actions/host_nic.py @@ -1,9 +1,4 @@ # © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK - -from typing import Dict, Optional - -from pydantic import BaseModel, ConfigDict - from primaite.game.agent.actions.manager import AbstractAction from primaite.interface.request import RequestFormat diff --git a/src/primaite/game/agent/actions/manager.py b/src/primaite/game/agent/actions/manager.py index 9621b7f0..d6b7d4b6 100644 --- a/src/primaite/game/agent/actions/manager.py +++ b/src/primaite/game/agent/actions/manager.py @@ -268,192 +268,6 @@ class ActionManager: """Return the gymnasium action space for this agent.""" return spaces.Discrete(len(self.action_map)) - # def get_node_name_by_idx(self, node_idx: int) -> str: - # """ - # Get the node name corresponding to the given index. - - # :param node_idx: The index of the node to retrieve. - # :type node_idx: int - # :return: The node hostname. - # :rtype: str - # """ - # if not node_idx < len(self.node_names): - # msg = ( - # f"Error: agent attempted to perform an action on node {node_idx}, but its action space only" - # f"has {len(self.node_names)} nodes." - # ) - # _LOGGER.error(msg) - # raise RuntimeError(msg) - # return self.node_names[node_idx] - - # def get_folder_name_by_idx(self, node_idx: int, folder_idx: int) -> Optional[str]: - # """ - # Get the folder name corresponding to the given node and folder indices. - - # :param node_idx: The index of the node. - # :type node_idx: int - # :param folder_idx: The index of the folder on the node. - # :type folder_idx: int - # :return: The name of the folder. Or None if the node has fewer folders than the given index. - # :rtype: Optional[str] - # """ - # if node_idx >= len(self.folder_names) or folder_idx >= len(self.folder_names[node_idx]): - # msg = ( - # f"Error: agent attempted to perform an action on node {node_idx} and folder {folder_idx}, but this" - # f" is out of range for its action space. Folder on each node: {self.folder_names}" - # ) - # _LOGGER.error(msg) - # raise RuntimeError(msg) - # return self.folder_names[node_idx][folder_idx] - - # def get_file_name_by_idx(self, node_idx: int, folder_idx: int, file_idx: int) -> Optional[str]: - # """Get the file name corresponding to the given node, folder, and file indices. - - # :param node_idx: The index of the node. - # :type node_idx: int - # :param folder_idx: The index of the folder on the node. - # :type folder_idx: int - # :param file_idx: The index of the file in the folder. - # :type file_idx: int - # :return: The name of the file. Or None if the node has fewer folders than the given index, or the folder has - # fewer files than the given index. - # :rtype: Optional[str] - # """ - # if ( - # node_idx >= len(self.file_names) - # or folder_idx >= len(self.file_names[node_idx]) - # or file_idx >= len(self.file_names[node_idx][folder_idx]) - # ): - # msg = ( - # f"Error: agent attempted to perform an action on node {node_idx} folder {folder_idx} file {file_idx}" - # f" but this is out of range for its action space. Files on each node: {self.file_names}" - # ) - # _LOGGER.error(msg) - # raise RuntimeError(msg) - # return self.file_names[node_idx][folder_idx][file_idx] - - # def get_service_name_by_idx(self, node_idx: int, service_idx: int) -> Optional[str]: - # """Get the service name corresponding to the given node and service indices. - - # :param node_idx: The index of the node. - # :type node_idx: int - # :param service_idx: The index of the service on the node. - # :type service_idx: int - # :return: The name of the service. Or None if the node has fewer services than the given index. - # :rtype: Optional[str] - # """ - # if node_idx >= len(self.service_names) or service_idx >= len(self.service_names[node_idx]): - # msg = ( - # f"Error: agent attempted to perform an action on node {node_idx} and service {service_idx}, but this" - # f" is out of range for its action space. Services on each node: {self.service_names}" - # ) - # _LOGGER.error(msg) - # raise RuntimeError(msg) - # return self.service_names[node_idx][service_idx] - - # def get_application_name_by_idx(self, node_idx: int, application_idx: int) -> Optional[str]: - # """Get the application name corresponding to the given node and service indices. - - # :param node_idx: The index of the node. - # :type node_idx: int - # :param application_idx: The index of the service on the node. - # :type application_idx: int - # :return: The name of the service. Or None if the node has fewer services than the given index. - # :rtype: Optional[str] - # """ - # if node_idx >= len(self.application_names) or application_idx >= len(self.application_names[node_idx]): - # msg = ( - # f"Error: agent attempted to perform an action on node {node_idx} and app {application_idx}, but " - # f"this is out of range for its action space. Applications on each node: {self.application_names}" - # ) - # _LOGGER.error(msg) - # raise RuntimeError(msg) - # return self.application_names[node_idx][application_idx] - - # def get_internet_protocol_by_idx(self, protocol_idx: int) -> str: - # """Get the internet protocol corresponding to the given index. - - # :param protocol_idx: The index of the protocol to retrieve. - # :type protocol_idx: int - # :return: The protocol. - # :rtype: str - # """ - # if protocol_idx >= len(self.protocols): - # msg = ( - # f"Error: agent attempted to perform an action on protocol {protocol_idx} but this" - # f" is out of range for its action space. Protocols: {self.protocols}" - # ) - # _LOGGER.error(msg) - # raise RuntimeError(msg) - # return self.protocols[protocol_idx] - - # def get_ip_address_by_idx(self, ip_idx: int) -> str: - # """ - # Get the IP address corresponding to the given index. - - # :param ip_idx: The index of the IP address to retrieve. - # :type ip_idx: int - # :return: The IP address. - # :rtype: str - # """ - # if ip_idx >= len(self.ip_address_list): - # msg = ( - # f"Error: agent attempted to perform an action on ip address {ip_idx} but this" - # f" is out of range for its action space. IP address list: {self.ip_address_list}" - # ) - # _LOGGER.error(msg) - # raise RuntimeError(msg) - # return self.ip_address_list[ip_idx] - - # def get_wildcard_by_idx(self, wildcard_idx: int) -> str: - # """ - # Get the IP wildcard corresponding to the given index. - - # :param ip_idx: The index of the IP wildcard to retrieve. - # :type ip_idx: int - # :return: The wildcard address. - # :rtype: str - # """ - # if wildcard_idx >= len(self.wildcard_list): - # msg = ( - # f"Error: agent attempted to perform an action on ip wildcard {wildcard_idx} but this" - # f" is out of range for its action space. Wildcard list: {self.wildcard_list}" - # ) - # _LOGGER.error(msg) - # raise RuntimeError(msg) - # return self.wildcard_list[wildcard_idx] - - # def get_port_by_idx(self, port_idx: int) -> str: - # """ - # Get the port corresponding to the given index. - - # :param port_idx: The index of the port to retrieve. - # :type port_idx: int - # :return: The port. - # :rtype: str - # """ - # if port_idx >= len(self.ports): - # msg = ( - # f"Error: agent attempted to perform an action on port {port_idx} but this" - # f" is out of range for its action space. Port list: {self.ip_address_list}" - # ) - # _LOGGER.error(msg) - # raise RuntimeError(msg) - # return self.ports[port_idx] - - # def get_nic_num_by_idx(self, node_idx: int, nic_idx: int) -> int: - # """ - # Get the NIC number corresponding to the given node and NIC indices. - - # :param node_idx: The index of the node. - # :type node_idx: int - # :param nic_idx: The index of the NIC on the node. - # :type nic_idx: int - # :return: The NIC number. - # :rtype: int - # """ - # return nic_idx + 1 - @classmethod def from_config(cls, game: "PrimaiteGame", cfg: Dict) -> "ActionManager": """ diff --git a/src/primaite/game/agent/actions/session.py b/src/primaite/game/agent/actions/session.py new file mode 100644 index 00000000..9fd20a0c --- /dev/null +++ b/src/primaite/game/agent/actions/session.py @@ -0,0 +1,64 @@ +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +from abc import abstractmethod +from typing import ClassVar + +from primaite.game.agent.actions.manager import AbstractAction +from primaite.interface.request import RequestFormat + + +class NodeSessionAbstractAction(AbstractAction): + """Base class for NodeSession actions.""" + + class ConfigSchema(AbstractAction.ConfigSchema): + """Base configuration schema for NodeSessionAbstractActions.""" + + node_name: str + remote_ip: str + + @abstractmethod + @classmethod + def form_request(cls, config: ConfigSchema) -> RequestFormat: + """Abstract method. Should return the action formatted as a request which can be ingested by the PrimAITE simulation.""" + if config.node_name is None or config.remote_ip is None: + return ["do_nothing"] + + +class NodeSessionsRemoteLoginAction(AbstractAction, identifier="node_session_remote_login"): + """Action which performs a remote session login.""" + + class ConfigSchema(NodeSessionAbstractAction.ConfigSchema): + """Configuration schema for NodeSessionsRemoteLoginAction.""" + username: str + password: str + + @classmethod + def form_request(cls, config: ConfigSchema) -> RequestFormat: + """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" + if config.node_name is None or config.remote_ip is None: + return ["do_nothing"] + return [ + "network", + "node", + config.node_name, + "service", + "Terminal", + "ssh_to_remote", + config.username, + config.password, + config.remote_ip, + ] + + +class NodeSessionsRemoteLogoutAction(AbstractAction, identifier="node_session_remote_logout"): + """Action which performs a remote session logout.""" + + class ConfigSchema(NodeSessionAbstractAction.ConfigSchema): + """Configuration schema for NodeSessionsRemoteLogoutAction.""" + pass + + @classmethod + def form_request(cls, config: ConfigSchema) -> RequestFormat: + """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" + if config.node_name is None or config.remote_ip is None: + return ["do_nothing"] + return ["network", "node", config.node_name, "service", "Terminal", "remote_logoff", config.remote_ip] \ No newline at end of file From bbcbb26f5edd79202bfed16b46a7bc3573b60397 Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Mon, 21 Oct 2024 14:43:51 +0100 Subject: [PATCH 09/58] #2913: Fix ActionPenalty. --- src/primaite/game/agent/rewards.py | 35 +++++++++++++++++------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/src/primaite/game/agent/rewards.py b/src/primaite/game/agent/rewards.py index 4198af27..1158d919 100644 --- a/src/primaite/game/agent/rewards.py +++ b/src/primaite/game/agent/rewards.py @@ -28,7 +28,7 @@ the structure: ``` """ from abc import ABC, abstractmethod -from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Type, TYPE_CHECKING, Union +from typing import Any, ClassVar, Callable, Dict, Iterable, List, Optional, Tuple, Type, TYPE_CHECKING, Union from pydantic import BaseModel from typing_extensions import Never @@ -51,6 +51,8 @@ class AbstractReward(BaseModel): type: str + _registry: ClassVar[Dict[str, Type["AbstractReward"]]] = {} + def __init_subclass__(cls, identifier: str, **kwargs: Any) -> None: super().__init_subclass__(**kwargs) if identifier in cls._registry: @@ -58,7 +60,6 @@ class AbstractReward(BaseModel): cls._registry[identifier] = cls @classmethod - @abstractmethod def from_config(cls, config: Dict) -> "AbstractReward": """Create a reward function component from a config dictionary. @@ -69,7 +70,8 @@ class AbstractReward(BaseModel): """ if config["type"] not in cls._registry: raise ValueError(f"Invalid reward type {config['type']}") - + adder_class = cls._registry[config["type"]] + adder_class.add_nodes_to_net(config=adder_class.ConfigSchema(**config)) return cls @abstractmethod @@ -276,7 +278,7 @@ class WebpageUnavailablePenalty(AbstractReward, identifier="WebpageUnavailablePe class ConfigSchema(AbstractReward.ConfigSchema): """ConfigSchema for WebpageUnavailablePenalty.""" - node_hostname: str + node_hostname: str = "" sticky: bool = True def __init__(self, node_hostname: str, sticky: bool = True) -> None: @@ -494,6 +496,8 @@ class SharedReward(AbstractReward, identifier="SharedReward"): class ActionPenalty(AbstractReward, identifier="ActionPenalty"): """Apply a negative reward when taking any action except DONOTHING.""" + action_penalty: float = -1.0 + do_nothing_penalty: float = 0.0 class ConfigSchema(AbstractReward.ConfigSchema): """Config schema for ActionPenalty.""" @@ -501,19 +505,20 @@ class ActionPenalty(AbstractReward, identifier="ActionPenalty"): action_penalty: float = -1.0 do_nothing_penalty: float = 0.0 - def __init__(self, action_penalty: float, do_nothing_penalty: float) -> None: - """ - Initialise the reward. + # def __init__(self, action_penalty: float, do_nothing_penalty: float) -> None: + # """ + # Initialise the reward. - Reward or penalise agents for doing nothing or taking actions. + # Reward or penalise agents for doing nothing or taking actions. - :param action_penalty: Reward to give agents for taking any action except DONOTHING - :type action_penalty: float - :param do_nothing_penalty: Reward to give agent for taking the DONOTHING action - :type do_nothing_penalty: float - """ - self.action_penalty = action_penalty - self.do_nothing_penalty = do_nothing_penalty + # :param action_penalty: Reward to give agents for taking any action except DONOTHING + # :type action_penalty: float + # :param do_nothing_penalty: Reward to give agent for taking the DONOTHING action + # :type do_nothing_penalty: float + # """ + # super().__init__(action_penalty=action_penalty, do_nothing_penalty=do_nothing_penalty) + # self.action_penalty = action_penalty + # self.do_nothing_penalty = do_nothing_penalty def calculate(self, state: Dict, last_action_response: "AgentHistoryItem") -> float: """Calculate the penalty to be applied. From 0cf8e20e6da5d8941dec6080ca687649437106a0 Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Mon, 21 Oct 2024 17:11:11 +0100 Subject: [PATCH 10/58] #2913: Update reward classes to work with pydantic. --- src/primaite/game/agent/rewards.py | 110 ++++++++++-------- .../game_layer/test_rewards.py | 2 +- 2 files changed, 62 insertions(+), 50 deletions(-) diff --git a/src/primaite/game/agent/rewards.py b/src/primaite/game/agent/rewards.py index 1158d919..9777441b 100644 --- a/src/primaite/game/agent/rewards.py +++ b/src/primaite/game/agent/rewards.py @@ -274,28 +274,34 @@ class WebServer404Penalty(AbstractReward, identifier="WebServer404Penalty"): class WebpageUnavailablePenalty(AbstractReward, identifier="WebpageUnavailablePenalty"): """Penalises the agent when the web browser fails to fetch a webpage.""" + node_hostname: str = "" + sticky: bool = True + reward: float = 0.0 + location_in_state: List[str] = [""] + _node: str = node_hostname class ConfigSchema(AbstractReward.ConfigSchema): """ConfigSchema for WebpageUnavailablePenalty.""" node_hostname: str = "" sticky: bool = True + reward: float = 0.0 - def __init__(self, node_hostname: str, sticky: bool = True) -> None: - """ - Initialise the reward component. + # def __init__(self, node_hostname: str, sticky: bool = True) -> None: + # """ + # Initialise the reward component. - :param node_hostname: Hostname of the node which has the web browser. - :type node_hostname: str - :param sticky: If True, calculate the reward based on the most recent response status. If False, only calculate - the reward if there were any responses this timestep. - :type sticky: bool - """ - self._node: str = node_hostname - self.location_in_state: List[str] = ["network", "nodes", node_hostname, "applications", "WebBrowser"] - self.sticky: bool = sticky - self.reward: float = 0.0 - """Reward value calculated last time any responses were seen. Used for persisting sticky rewards.""" + # :param node_hostname: Hostname of the node which has the web browser. + # :type node_hostname: str + # :param sticky: If True, calculate the reward based on the most recent response status. If False, only calculate + # the reward if there were any responses this timestep. + # :type sticky: bool + # """ + # self._node: str = node_hostname + # self.location_in_state: List[str] = ["network", "nodes", node_hostname, "applications", "WebBrowser"] + # self.sticky: bool = sticky + # self.reward: float = 0.0 + # """Reward value calculated last time any responses were seen. Used for persisting sticky rewards.""" def calculate(self, state: Dict, last_action_response: "AgentHistoryItem") -> float: """ @@ -311,6 +317,7 @@ class WebpageUnavailablePenalty(AbstractReward, identifier="WebpageUnavailablePe :return: Reward value :rtype: float """ + self.location_in_state: List[str] = ["network", "nodes", self.node_hostname, "applications", "WebBrowser"] web_browser_state = access_from_nested_dict(state, self.location_in_state) if web_browser_state is NOT_PRESENT_IN_STATE: @@ -364,6 +371,10 @@ class WebpageUnavailablePenalty(AbstractReward, identifier="WebpageUnavailablePe class GreenAdminDatabaseUnreachablePenalty(AbstractReward, identifier="GreenAdminDatabaseUnreachablePenalty"): """Penalises the agent when the green db clients fail to connect to the database.""" + node_hostname: str = "" + _node: str = node_hostname + sticky: bool = True + reward: float = 0.0 class ConfigSchema(AbstractReward.ConfigSchema): """ConfigSchema for GreenAdminDatabaseUnreachablePenalty.""" @@ -371,21 +382,21 @@ class GreenAdminDatabaseUnreachablePenalty(AbstractReward, identifier="GreenAdmi node_hostname: str sticky: bool = True - def __init__(self, node_hostname: str, sticky: bool = True) -> None: - """ - Initialise the reward component. + # def __init__(self, node_hostname: str, sticky: bool = True) -> None: + # """ + # Initialise the reward component. - :param node_hostname: Hostname of the node where the database client sits. - :type node_hostname: str - :param sticky: If True, calculate the reward based on the most recent response status. If False, only calculate - the reward if there were any responses this timestep. - :type sticky: bool - """ - self._node: str = node_hostname - self.location_in_state: List[str] = ["network", "nodes", node_hostname, "applications", "DatabaseClient"] - self.sticky: bool = sticky - self.reward: float = 0.0 - """Reward value calculated last time any responses were seen. Used for persisting sticky rewards.""" + # :param node_hostname: Hostname of the node where the database client sits. + # :type node_hostname: str + # :param sticky: If True, calculate the reward based on the most recent response status. If False, only calculate + # the reward if there were any responses this timestep. + # :type sticky: bool + # """ + # self._node: str = node_hostname + # self.location_in_state: List[str] = ["network", "nodes", node_hostname, "applications", "DatabaseClient"] + # self.sticky: bool = sticky + # self.reward: float = 0.0 + # """Reward value calculated last time any responses were seen. Used for persisting sticky rewards.""" def calculate(self, state: Dict, last_action_response: "AgentHistoryItem") -> float: """ @@ -438,37 +449,38 @@ class GreenAdminDatabaseUnreachablePenalty(AbstractReward, identifier="GreenAdmi class SharedReward(AbstractReward, identifier="SharedReward"): """Adds another agent's reward to the overall reward.""" + agent_name: str class ConfigSchema(AbstractReward.ConfigSchema): """Config schema for SharedReward.""" agent_name: str - def __init__(self, agent_name: Optional[str] = None) -> None: + # def __init__(self, agent_name: Optional[str] = None) -> None: + # """ + # Initialise the shared reward. + + # The agent_name is a placeholder value. It starts off as none, but it must be set before this reward can work + # correctly. + + # :param agent_name: The name whose reward is an input + # :type agent_name: Optional[str] + # """ + # # self.agent_name = agent_name + # """Agent whose reward to track.""" + + def default_callback(agent_name: str) -> Never: """ - Initialise the shared reward. + Default callback to prevent calling this reward until it's properly initialised. - The agent_name is a placeholder value. It starts off as none, but it must be set before this reward can work - correctly. - - :param agent_name: The name whose reward is an input - :type agent_name: Optional[str] + SharedReward should not be used until the game layer replaces self.callback with a reference to the + function that retrieves the desired agent's reward. Therefore, we define this default callback that raises + an error. """ - self.agent_name = agent_name - """Agent whose reward to track.""" + raise RuntimeError("Attempted to calculate SharedReward but it was not initialised properly.") - def default_callback(agent_name: str) -> Never: - """ - Default callback to prevent calling this reward until it's properly initialised. - - SharedReward should not be used until the game layer replaces self.callback with a reference to the - function that retrieves the desired agent's reward. Therefore, we define this default callback that raises - an error. - """ - raise RuntimeError("Attempted to calculate SharedReward but it was not initialised properly.") - - self.callback: Callable[[str], float] = default_callback - """Method that retrieves an agent's current reward given the agent's name.""" + callback: Callable[[str], float] = default_callback + """Method that retrieves an agent's current reward given the agent's name.""" def calculate(self, state: Dict, last_action_response: "AgentHistoryItem") -> float: """Simply access the other agent's reward and return it. diff --git a/tests/integration_tests/game_layer/test_rewards.py b/tests/integration_tests/game_layer/test_rewards.py index 0005b508..bf707feb 100644 --- a/tests/integration_tests/game_layer/test_rewards.py +++ b/tests/integration_tests/game_layer/test_rewards.py @@ -74,7 +74,7 @@ def test_uc2_rewards(game_and_agent): ACLAction.PERMIT, src_port=PORT_LOOKUP["POSTGRES_SERVER"], dst_port=PORT_LOOKUP["POSTGRES_SERVER"], position=2 ) - comp = GreenAdminDatabaseUnreachablePenalty("client_1") + comp = GreenAdminDatabaseUnreachablePenalty(node_hostname="client_1") request = ["network", "node", "client_1", "application", "DatabaseClient", "execute"] response = game.simulation.apply_request(request) From 11357f87caacfe8531b5b4b0e707456a009e7dfc Mon Sep 17 00:00:00 2001 From: Charlie Crane Date: Mon, 21 Oct 2024 17:51:55 +0100 Subject: [PATCH 11/58] #2912 - eod commit. Addressing test and lint errors for refactored actions --- src/primaite/game/agent/actions/__init__.py | 57 +++++------- src/primaite/game/agent/actions/acl.py | 93 +++++++------------ .../game/agent/actions/application.py | 19 ++-- src/primaite/game/agent/actions/config.py | 72 ++++++++------ src/primaite/game/agent/actions/file.py | 11 ++- src/primaite/game/agent/actions/folder.py | 15 ++- src/primaite/game/agent/actions/host_nic.py | 4 +- src/primaite/game/agent/actions/manager.py | 12 ++- src/primaite/game/agent/actions/network.py | 17 ++-- src/primaite/game/agent/actions/node.py | 7 +- src/primaite/game/agent/actions/service.py | 32 ++++++- src/primaite/game/agent/actions/session.py | 23 +++-- .../game/agent/observations/__init__.py | 2 +- 13 files changed, 204 insertions(+), 160 deletions(-) diff --git a/src/primaite/game/agent/actions/__init__.py b/src/primaite/game/agent/actions/__init__.py index 9d6c0fcc..428c6c58 100644 --- a/src/primaite/game/agent/actions/__init__.py +++ b/src/primaite/game/agent/actions/__init__.py @@ -1,40 +1,31 @@ # © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK -from primaite.game.agent.actions.manager import ActionManager -from primaite.game.agent.actions.service import ( - NodeServiceDisableAction, - NodeServiceEnableAction, - NodeServiceFixAction, - NodeServicePauseAction, - NodeServiceRestartAction, - NodeServiceResumeAction, - NodeServiceScanAction, - NodeServiceStartAction, - NodeServiceStopAction, +from primaite.game.agent.actions import ( + acl, + application, + config, + file, + folder, + host_nic, + manager, + network, + node, + service, + session, ) +from primaite.game.agent.actions.manager import ActionManager __all__ = ( - "NodeServiceDisableAction", - "NodeServiceEnableAction", - "NodeServiceFixAction", - "NodeServicePauseAction", - "NodeServiceRestartAction", - "NodeServiceResumeAction", - "NodeServiceScanAction", - "NodeServiceStartAction", - "NodeServiceStopAction", + "acl", + "application", + "config", + "file", + "folder", + "host_nic", + "manager", + "network", + "node", + "service", + "session", "ActionManager", ) - -# __all__ = ( -# "acl", -# "application", -# "config", -# "file", -# "folder", -# "host_nic", -# "manager", -# "network", -# "node", -# "service", -# ) diff --git a/src/primaite/game/agent/actions/acl.py b/src/primaite/game/agent/actions/acl.py index 050e94e8..1048dc1e 100644 --- a/src/primaite/game/agent/actions/acl.py +++ b/src/primaite/game/agent/actions/acl.py @@ -4,13 +4,28 @@ from typing import Dict, List, Literal from pydantic import BaseModel, Field, field_validator, ValidationInfo from primaite.game.agent.actions.manager import AbstractAction -from primaite.game.game import _LOGGER from primaite.interface.request import RequestFormat +__all__ = ("RouterACLAddRuleAction", "RouterACLRemoveRuleAction", "FirewallACLAddRuleAction", "FirewallACLRemoveRuleAction") class RouterACLAddRuleAction(AbstractAction, identifier="router_acl_add_rule"): """Action which adds a rule to a router's ACL.""" + class ConfigSchema(AbstractAction.ConfigSchema): + """Configuration Schema for RouterACLAddRuleAction.""" + + target_router: str + position: int + permission: Literal[1, 2] + source_ip_id: int + source_wildcard_id: int + source_port_id: int + dest_ip_id: int + dest_wildcard_id: int + dest_port_id: int + protocol_id: int + + class ACLRuleOptions(BaseModel): """Validator for ACL_ADD_RULE options.""" @@ -52,73 +67,31 @@ class RouterACLAddRuleAction(AbstractAction, identifier="router_acl_add_rule"): return cls.model_fields[info.field_name].default return v - def __init__( - self, - manager: "ActionManager", - max_acl_rules: int, - num_ips: int, - num_ports: int, - num_protocols: int, - **kwargs, - ) -> None: - """Init method for RouterACLAddRuleAction. - - :param manager: Reference to the ActionManager which created this action. - :type manager: ActionManager - :param max_acl_rules: Maximum number of ACL rules that can be added to the router. - :type max_acl_rules: int - :param num_ips: Number of IP addresses in the simulation. - :type num_ips: int - :param num_ports: Number of ports in the simulation. - :type num_ports: int - :param num_protocols: Number of protocols in the simulation. - :type num_protocols: int - """ - super().__init__(manager=manager) - num_permissions = 3 - self.shape: Dict[str, int] = { - "position": max_acl_rules, - "permission": num_permissions, - "source_ip_id": num_ips, - "dest_ip_id": num_ips, - "source_port_id": num_ports, - "dest_port_id": num_ports, - "protocol_id": num_protocols, - } - + @classmethod def form_request( - self, - target_router: str, - position: int, - permission: int, - source_ip_id: int, - source_wildcard_id: int, - dest_ip_id: int, - dest_wildcard_id: int, - source_port_id: int, - dest_port_id: int, - protocol_id: int, + cls, + config: ConfigSchema ) -> List[str]: """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" # Validate incoming data. parsed_options = RouterACLAddRuleAction.ACLRuleOptions( - target_router=target_router, - position=position, - permission=permission, - source_ip_id=source_ip_id, - source_wildcard_id=source_wildcard_id, - dest_ip_id=dest_ip_id, - dest_wildcard_id=dest_wildcard_id, - source_port_id=source_port_id, - dest_port_id=dest_port_id, - protocol_id=protocol_id, + target_router=config.target_router, + position=config.position, + permission=config.permission, + source_ip_id=config.source_ip_id, + source_wildcard_id=config.source_wildcard_id, + dest_ip_id=config.dest_ip_id, + dest_wildcard_id=config.dest_wildcard_id, + source_port_id=config.source_port_id, + dest_port_id=config.dest_port_id, + protocol_id=config.protocol_id, ) if parsed_options.permission == 1: permission_str = "PERMIT" elif parsed_options.permission == 2: permission_str = "DENY" - else: - _LOGGER.warning(f"{self.__class__} received permission {permission}, expected 0 or 1.") + # else: + # _LOGGER.warning(f"{self.__class__} received permission {permission}, expected 0 or 1.") if parsed_options.protocol_id == 1: protocol = "ALL" @@ -246,8 +219,8 @@ class FirewallACLAddRuleAction(AbstractAction, identifier="firewall_acl_add_rule permission_str = "PERMIT" elif permission == 2: permission_str = "DENY" - else: - _LOGGER.warning(f"{self.__class__} received permission {permission}, expected 0 or 1.") + # else: + # _LOGGER.warning(f"{self.__class__} received permission {permission}, expected 0 or 1.") if protocol_id == 0: return ["do_nothing"] # NOT SUPPORTED, JUST DO NOTHING IF WE COME ACROSS THIS diff --git a/src/primaite/game/agent/actions/application.py b/src/primaite/game/agent/actions/application.py index 110b71da..3a254d57 100644 --- a/src/primaite/game/agent/actions/application.py +++ b/src/primaite/game/agent/actions/application.py @@ -4,8 +4,17 @@ from typing import ClassVar from primaite.game.agent.actions.manager import AbstractAction from primaite.interface.request import RequestFormat +__all__ = ( + "NodeApplicationExecuteAction", + "NodeApplicationScanAction", + "NodeApplicationCloseAction", + "NodeApplicationFixAction", + "NodeApplicationInstallAction", + "NodeApplicationRemoveAction", +) -class NodeApplicationAbstractAction(AbstractAction): + +class NodeApplicationAbstractAction(AbstractAction, identifier="node_application_abstract_action"): """ Base class for application actions. @@ -65,7 +74,7 @@ class NodeApplicationFixAction(NodeApplicationAbstractAction, identifier="node_a verb: str = "fix" -class NodeApplicationInstallAction(NodeApplicationAbstractAction): +class NodeApplicationInstallAction(NodeApplicationAbstractAction, identifier="node_application_install"): """Action which installs an application.""" class ConfigSchema(NodeApplicationAbstractAction.ConfigSchema): @@ -76,12 +85,10 @@ class NodeApplicationInstallAction(NodeApplicationAbstractAction): # TODO: Either changes to application form_request bits, or add that here. -class NodeApplicationRemoveAction(NodeApplicationAbstractAction): - """Action which removes/uninstalls an application""" +class NodeApplicationRemoveAction(NodeApplicationAbstractAction, identifier="node_application_remove"): + """Action which removes/uninstalls an application.""" class ConfigSchema(NodeApplicationAbstractAction.ConfigSchema): """Configuration schema for NodeApplicationRemoveAction.""" verb: str = "uninstall" - - # TODO: Either changes to application form_request bits, or add that here. diff --git a/src/primaite/game/agent/actions/config.py b/src/primaite/game/agent/actions/config.py index d627e4b0..e92d443b 100644 --- a/src/primaite/game/agent/actions/config.py +++ b/src/primaite/game/agent/actions/config.py @@ -7,31 +7,39 @@ from pydantic import BaseModel, ConfigDict, Field, field_validator, ValidationIn from primaite.game.agent.actions.manager import AbstractAction from primaite.interface.request import RequestFormat +__all__ = ( + "ConfigureRansomwareScriptAction", + "ConfigureDoSBotAction", + "ConfigureC2BeaconAction", + "NodeSendRemoteCommandAction", + "TerminalC2ServerAction", + "RansomwareLaunchC2ServerAction", + "ExfiltrationC2ServerAction", +) -class ConfigureRansomwareScriptAction(AbstractAction): + +class ConfigureRansomwareScriptAction(AbstractAction, identifier="configure_ransomware"): """Action which sets config parameters for a ransomware script on a node.""" - class _Opts(BaseModel): - """Schema for options that can be passed to this option.""" + class ConfigSchema(AbstractAction.ConfigSchema): + """Configuration schema for ConfigureRansomwareScriptAction.""" model_config = ConfigDict(extra="forbid") - server_ip_address: Optional[str] = None - server_password: Optional[str] = None - payload: Optional[str] = None + node_name: str + server_ip_address: Optional[str] + server_password: Optional[str] + payload: Optional[str] - def __init__(self, manager: "ActionManager", **kwargs) -> None: - super().__init__(manager=manager) - - def form_request(self, node_id: int, config: Dict) -> RequestFormat: + @classmethod + def form_request(cls, config: ConfigSchema) -> RequestFormat: """Return the action formatted as a request that can be ingested by the simulation.""" - node_name = self.manager.get_node_name_by_idx(node_id) - if node_name is None: + if config.node_name is None: return ["do_nothing"] ConfigureRansomwareScriptAction._Opts.model_validate(config) # check that options adhere to schema - return ["network", "node", node_name, "application", "RansomwareScript", "configure", config] + return ["network", "node", config.node_name, "application", "RansomwareScript", "configure", config] -class ConfigureDoSBotAction(AbstractAction): +class ConfigureDoSBotAction(AbstractAction, identifier="configure_dos_bot"): """Action which sets config parameters for a DoS bot on a node.""" class _Opts(BaseModel): @@ -58,7 +66,7 @@ class ConfigureDoSBotAction(AbstractAction): return ["network", "node", node_name, "application", "DoSBot", "configure", config] -class ConfigureC2BeaconAction(AbstractAction): +class ConfigureC2BeaconAction(AbstractAction, identifier="configure_c2"): """Action which configures a C2 Beacon based on the parameters given.""" class ConfigSchema(AbstractAction.ConfigSchema): @@ -109,28 +117,32 @@ class ConfigureC2BeaconAction(AbstractAction): return ["network", "node", config.node_name, "application", "C2Beacon", "configure", config.__dict__] -class NodeSendRemoteCommandAction(AbstractAction): +class NodeSendRemoteCommandAction(AbstractAction, identifier="node_send_remote_command"): """Action which sends a terminal command to a remote node via SSH.""" - def __init__(self, manager: "ActionManager", **kwargs) -> None: - super().__init__(manager=manager) + class ConfigSchema(AbstractAction.ConfigSchema): + """Configuration schema for NodeSendRemoteCommandAction.""" - def form_request(self, node_id: int, remote_ip: str, command: RequestFormat) -> RequestFormat: + node_name: str + remote_ip: str + command: RequestFormat + + @classmethod + def form_request(cls, config: ConfigSchema) -> RequestFormat: """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - node_name = self.manager.get_node_name_by_idx(node_id) return [ "network", "node", - node_name, + config.node_name, "service", "Terminal", "send_remote_command", - remote_ip, - {"command": command}, + config.remote_ip, + {"command": config.command}, ] -class TerminalC2ServerAction(AbstractAction): +class TerminalC2ServerAction(AbstractAction, identifier="terminal_c2_server"): """Action which causes the C2 Server to send a command to the C2 Beacon to execute the terminal command passed.""" class _Opts(BaseModel): @@ -161,11 +173,12 @@ class TerminalC2ServerAction(AbstractAction): return ["network", "node", node_name, "application", "C2Server", "terminal_command", command_model] -class RansomwareLaunchC2ServerAction(AbstractAction): +class RansomwareLaunchC2ServerAction(AbstractAction, identifier="ransomware_launch"): """Action which causes the C2 Server to send a command to the C2 Beacon to launch the RansomwareScript.""" - class ConfigSchema(AbstractAction): + class ConfigSchema(AbstractAction.ConfigSchema): """Configuration schema for RansomwareLaunchC2ServerAction.""" + node_name: str @classmethod @@ -175,8 +188,9 @@ class RansomwareLaunchC2ServerAction(AbstractAction): return ["do_nothing"] # This action currently doesn't require any further configuration options. return ["network", "node", config.node_name, "application", "C2Server", "ransomware_launch"] - -class ExfiltrationC2ServerAction(AbstractAction): + + +class ExfiltrationC2ServerAction(AbstractAction, identifier="exfiltration_c2_server"): """Action which exfiltrates a target file from a certain node onto the C2 beacon and then the C2 Server.""" class _Opts(BaseModel): @@ -215,4 +229,4 @@ class ExfiltrationC2ServerAction(AbstractAction): "password": account["password"], } ExfiltrationC2ServerAction._Opts.model_validate(command_model) - return ["network", "node", node_name, "application", "C2Server", "exfiltrate", command_model] \ No newline at end of file + return ["network", "node", node_name, "application", "C2Server", "exfiltrate", command_model] diff --git a/src/primaite/game/agent/actions/file.py b/src/primaite/game/agent/actions/file.py index 77bd8ef3..6935a11c 100644 --- a/src/primaite/game/agent/actions/file.py +++ b/src/primaite/game/agent/actions/file.py @@ -4,8 +4,17 @@ from typing import ClassVar from primaite.game.agent.actions.manager import AbstractAction from primaite.interface.request import RequestFormat +__all__ = ( + "NodeFileCreateAction", + "NodeFileScanAction", + "NodeFileDeleteAction", + "NodeFileRestoreAction", + "NodeFileCorruptAction", + "NodeFileAccessAction", +) -class NodeFileAbstractAction(AbstractAction): + +class NodeFileAbstractAction(AbstractAction, identifier="node_file_abstract_action"): """Abstract base class for file actions. Any action which applies to a file and uses node_name, folder_name, and file_name as its diff --git a/src/primaite/game/agent/actions/folder.py b/src/primaite/game/agent/actions/folder.py index b9e003c7..74820eb0 100644 --- a/src/primaite/game/agent/actions/folder.py +++ b/src/primaite/game/agent/actions/folder.py @@ -1,16 +1,23 @@ # © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK -from abc import abstractmethod -from typing import ClassVar, Dict +from typing import ClassVar from primaite.game.agent.actions.manager import AbstractAction from primaite.interface.request import RequestFormat +__all__ = ( + "NodeFolderScanAction", + "NodeFolderCheckhashAction", + "NodeFolderRepairAction", + "NodeFolderRestoreAction", + "NodeFolderCreateAction", +) -class NodeFolderAbstractAction(AbstractAction): + +class NodeFolderAbstractAction(AbstractAction, identifier="node_folder_abstract"): """ Base class for folder actions. - Any action which applies to a folder and uses node_id and folder_id as its only two parameters can inherit from + Any action which applies to a folder and uses node_name and folder_name as its only two parameters can inherit from this base class. """ diff --git a/src/primaite/game/agent/actions/host_nic.py b/src/primaite/game/agent/actions/host_nic.py index 2e53cf72..4f66f9b9 100644 --- a/src/primaite/game/agent/actions/host_nic.py +++ b/src/primaite/game/agent/actions/host_nic.py @@ -2,8 +2,10 @@ from primaite.game.agent.actions.manager import AbstractAction from primaite.interface.request import RequestFormat +__all__ = ("HostNICEnableAction", "HostNICDisableAction") -class HostNICAbstractAction(AbstractAction): + +class HostNICAbstractAction(AbstractAction, identifier="host_nic_abstract"): """ Abstract base class for NIC actions. diff --git a/src/primaite/game/agent/actions/manager.py b/src/primaite/game/agent/actions/manager.py index d6b7d4b6..2f47ea7c 100644 --- a/src/primaite/game/agent/actions/manager.py +++ b/src/primaite/game/agent/actions/manager.py @@ -1,5 +1,5 @@ # © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK -"""yaml example +"""yaml example. agents: - name: agent_1 @@ -20,7 +20,7 @@ from typing import Any, ClassVar, Dict, List, Literal, Optional, Tuple, Type from gymnasium import spaces from pydantic import BaseModel, ConfigDict -from primaite.game.game import PrimaiteGame +# from primaite.game.game import PrimaiteGame # TODO: Breaks things from primaite.interface.request import RequestFormat # TODO: Make sure that actions are backwards compatible where the old YAML format is used. @@ -37,6 +37,8 @@ class AbstractAction(BaseModel): # CAOS actions to requests for simulator. Similar to the network node adder, that class also doesn't need to be # instantiated.) class ConfigSchema(BaseModel, ABC): # TODO: not sure if this better named something like `Options` + """Base configuration schema for Actions.""" + model_config = ConfigDict(extra="forbid") type: str @@ -54,8 +56,12 @@ class AbstractAction(BaseModel): return [] -class DoNothingAction(AbstractAction): +class DoNothingAction(AbstractAction, identifier="do_nothing"): + """Do Nothing Action.""" + class ConfigSchema(AbstractAction.ConfigSchema): + """Configuration Schema for DoNothingAction.""" + type: Literal["do_nothing"] = "do_nothing" def form_request(self, options: ConfigSchema) -> RequestFormat: diff --git a/src/primaite/game/agent/actions/network.py b/src/primaite/game/agent/actions/network.py index 630385bd..63eff218 100644 --- a/src/primaite/game/agent/actions/network.py +++ b/src/primaite/game/agent/actions/network.py @@ -1,18 +1,19 @@ # © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK -from typing import ClassVar, Dict, Optional - -from pydantic import BaseModel, ConfigDict +from typing import ClassVar from primaite.game.agent.actions.manager import AbstractAction from primaite.interface.request import RequestFormat +__all__ = ("NetworkPortEnableAction", "NetworkPortDisableAction") -class NetworkPortAbstractAction(AbstractAction): - """Base class for Network port actions""" + +class NetworkPortAbstractAction(AbstractAction, identifier="network_port_abstract"): + """Base class for Network port actions.""" class ConfigSchema(AbstractAction.ConfigSchema): """Base configuration schema for NetworkPort actions.""" + target_nodename: str port_id: str @@ -21,7 +22,7 @@ class NetworkPortAbstractAction(AbstractAction): @classmethod def form_request(cls, config: ConfigSchema) -> RequestFormat: """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - if config.target_nodename is None or config.port_id is None: + if config.target_nodename is None or config.port_id is None: return ["do_nothing"] return ["network", "node", config.target_nodename, "network_interface", config.port_id, cls.verb] @@ -34,11 +35,11 @@ class NetworkPortEnableAction(NetworkPortAbstractAction, identifier="network_por verb: str = "enable" + class NetworkPortDisableAction(NetworkPortAbstractAction, identifier="network_port_disable"): """Action which disables are port on a router or a firewall.""" class ConfigSchema(AbstractAction.ConfigSchema): - """Configuration schema for NetworkPortDisableAction""" + """Configuration schema for NetworkPortDisableAction.""" verb: str = "disable" - diff --git a/src/primaite/game/agent/actions/node.py b/src/primaite/game/agent/actions/node.py index d431d344..011ff4dc 100644 --- a/src/primaite/game/agent/actions/node.py +++ b/src/primaite/game/agent/actions/node.py @@ -1,12 +1,13 @@ # © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK -from abc import abstractmethod -from typing import ClassVar, Dict +from typing import ClassVar from primaite.game.agent.actions.manager import AbstractAction from primaite.interface.request import RequestFormat +__all__ = ("NodeOSScanAction", "NodeShutdownAction", "NodeStartupAction", "NodeResetAction") -class NodeAbstractAction(AbstractAction): + +class NodeAbstractAction(AbstractAction, identifier="node_abstract"): """ Abstract base class for node actions. diff --git a/src/primaite/game/agent/actions/service.py b/src/primaite/game/agent/actions/service.py index 97b37bde..cf277b5d 100644 --- a/src/primaite/game/agent/actions/service.py +++ b/src/primaite/game/agent/actions/service.py @@ -4,8 +4,20 @@ from typing import ClassVar from primaite.game.agent.actions.manager import AbstractAction from primaite.interface.request import RequestFormat +__all__ = ( + "NodeServiceScanAction", + "NodeServiceStopAction", + "NodeServiceStartAction", + "NodeServicePauseAction", + "NodeServiceResumeAction", + "NodeServiceRestartAction", + "NodeServiceDisableAction", + "NodeServiceEnableAction", + "NodeServiceFixAction", +) -class NodeServiceAbstractAction(AbstractAction): + +class NodeServiceAbstractAction(AbstractAction, identifier="node_service_abstract"): class ConfigSchema(AbstractAction.ConfigSchema): node_name: str service_name: str @@ -22,6 +34,8 @@ class NodeServiceScanAction(NodeServiceAbstractAction, identifier="node_service_ """Action which scans a service.""" class ConfigSchema(NodeServiceAbstractAction.ConfigSchema): + """Configuration Schema for NodeServiceScanAction""" + verb: str = "scan" @@ -29,6 +43,8 @@ class NodeServiceStopAction(NodeServiceAbstractAction, identifier="node_service_ """Action which stops a service.""" class ConfigSchema(NodeServiceAbstractAction.ConfigSchema): + """Configuration Schema for NodeServiceStopAction.""" + verb: str = "stop" @@ -36,6 +52,8 @@ class NodeServiceStartAction(NodeServiceAbstractAction, identifier="node_service """Action which starts a service.""" class ConfigSchema(NodeServiceAbstractAction.ConfigSchema): + """Configuration Schema for NodeServiceStartAction.""" + verb: str = "start" @@ -43,6 +61,8 @@ class NodeServicePauseAction(NodeServiceAbstractAction, identifier="node_service """Action which pauses a service.""" class ConfigSchema(NodeServiceAbstractAction.ConfigSchema): + """Configuration Schema for NodeServicePauseAction.""" + verb: str = "pause" @@ -50,6 +70,8 @@ class NodeServiceResumeAction(NodeServiceAbstractAction, identifier="node_servic """Action which resumes a service.""" class ConfigSchema(NodeServiceAbstractAction.ConfigSchema): + """Configuration Schema for NodeServiceResumeAction.""" + verb: str = "resume" @@ -57,6 +79,8 @@ class NodeServiceRestartAction(NodeServiceAbstractAction, identifier="node_servi """Action which restarts a service.""" class ConfigSchema(NodeServiceAbstractAction.ConfigSchema): + """Configuration Schema for NodeServiceRestartAction.""" + verb: str = "restart" @@ -64,6 +88,8 @@ class NodeServiceDisableAction(NodeServiceAbstractAction, identifier="node_servi """Action which disables a service.""" class ConfigSchema(NodeServiceAbstractAction.ConfigSchema): + """Configuration Schema for NodeServiceDisableAction.""" + verb: str = "disable" @@ -71,6 +97,8 @@ class NodeServiceEnableAction(NodeServiceAbstractAction, identifier="node_servic """Action which enables a service.""" class ConfigSchema(NodeServiceAbstractAction.ConfigSchema): + """Configuration Schema for NodeServiceEnableAction.""" + verb: str = "enable" @@ -78,4 +106,6 @@ class NodeServiceFixAction(NodeServiceAbstractAction, identifier="node_service_f """Action which fixes a service.""" class ConfigSchema(NodeServiceAbstractAction.ConfigSchema): + """Configuration Schema for NodeServiceFixAction.""" + verb: str = "fix" diff --git a/src/primaite/game/agent/actions/session.py b/src/primaite/game/agent/actions/session.py index 9fd20a0c..eb035ff3 100644 --- a/src/primaite/game/agent/actions/session.py +++ b/src/primaite/game/agent/actions/session.py @@ -1,33 +1,35 @@ # © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from abc import abstractmethod -from typing import ClassVar from primaite.game.agent.actions.manager import AbstractAction from primaite.interface.request import RequestFormat +__all__ = ("NodeSessionsRemoteLoginAction", "NodeSessionsRemoteLogoutAction") -class NodeSessionAbstractAction(AbstractAction): + +class NodeSessionAbstractAction(AbstractAction, identifier="node_session_abstract"): """Base class for NodeSession actions.""" class ConfigSchema(AbstractAction.ConfigSchema): """Base configuration schema for NodeSessionAbstractActions.""" - + node_name: str remote_ip: str - @abstractmethod @classmethod + @abstractmethod def form_request(cls, config: ConfigSchema) -> RequestFormat: - """Abstract method. Should return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - if config.node_name is None or config.remote_ip is None: - return ["do_nothing"] + """Abstract method. Should return the action formatted as a request which + can be ingested by the PrimAITE simulation.""" + pass -class NodeSessionsRemoteLoginAction(AbstractAction, identifier="node_session_remote_login"): +class NodeSessionsRemoteLoginAction(NodeSessionAbstractAction, identifier="node_session_remote_login"): """Action which performs a remote session login.""" class ConfigSchema(NodeSessionAbstractAction.ConfigSchema): """Configuration schema for NodeSessionsRemoteLoginAction.""" + username: str password: str @@ -49,11 +51,12 @@ class NodeSessionsRemoteLoginAction(AbstractAction, identifier="node_session_rem ] -class NodeSessionsRemoteLogoutAction(AbstractAction, identifier="node_session_remote_logout"): +class NodeSessionsRemoteLogoutAction(NodeSessionAbstractAction, identifier="node_session_remote_logout"): """Action which performs a remote session logout.""" class ConfigSchema(NodeSessionAbstractAction.ConfigSchema): """Configuration schema for NodeSessionsRemoteLogoutAction.""" + pass @classmethod @@ -61,4 +64,4 @@ class NodeSessionsRemoteLogoutAction(AbstractAction, identifier="node_session_re """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" if config.node_name is None or config.remote_ip is None: return ["do_nothing"] - return ["network", "node", config.node_name, "service", "Terminal", "remote_logoff", config.remote_ip] \ No newline at end of file + return ["network", "node", config.node_name, "service", "Terminal", "remote_logoff", config.remote_ip] diff --git a/src/primaite/game/agent/observations/__init__.py b/src/primaite/game/agent/observations/__init__.py index 6c88f844..c4811c98 100644 --- a/src/primaite/game/agent/observations/__init__.py +++ b/src/primaite/game/agent/observations/__init__.py @@ -17,5 +17,5 @@ from primaite.game.agent.observations.software_observation import ApplicationObs __all__ = [ "ACLObservation", "FileObservation", "FolderObservation", "FirewallObservation", "HostObservation", "LinksObservation", "NICObservation", "PortObservation", "NodesObservation", "NestedObservation", - "ObservationManager", "ApplicationObservation", "ServiceObservation",] + "ObservationManager", "ApplicationObservation", "ServiceObservation", "RouterObservation", "LinkObservation",] # fmt: on From f95ba8cbbcd92779a66684c6bd2dbbf51052e5d2 Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Tue, 22 Oct 2024 11:01:35 +0100 Subject: [PATCH 12/58] #2913: Fix remaining pydantic errors. --- src/primaite/game/agent/rewards.py | 141 ++++++------------ .../_game/_agent/test_sticky_rewards.py | 12 +- 2 files changed, 48 insertions(+), 105 deletions(-) diff --git a/src/primaite/game/agent/rewards.py b/src/primaite/game/agent/rewards.py index 9777441b..1f870e83 100644 --- a/src/primaite/game/agent/rewards.py +++ b/src/primaite/game/agent/rewards.py @@ -28,7 +28,7 @@ the structure: ``` """ from abc import ABC, abstractmethod -from typing import Any, ClassVar, Callable, Dict, Iterable, List, Optional, Tuple, Type, TYPE_CHECKING, Union +from typing import Any, Callable, ClassVar, Dict, Iterable, List, Optional, Tuple, Type, TYPE_CHECKING, Union from pydantic import BaseModel from typing_extensions import Never @@ -118,6 +118,12 @@ class DummyReward(AbstractReward, identifier="DummyReward"): class DatabaseFileIntegrity(AbstractReward, identifier="DatabaseFileIntegrity"): """Reward function component which rewards the agent for maintaining the integrity of a database file.""" + node_hostname: str + folder_name: str + file_name: str + location_in_state: List[str] = [""] + reward: float = 0.0 + class ConfigSchema(AbstractReward.ConfigSchema): """ConfigSchema for DatabaseFileIntegrity.""" @@ -125,27 +131,6 @@ class DatabaseFileIntegrity(AbstractReward, identifier="DatabaseFileIntegrity"): folder_name: str file_name: str - def __init__(self, node_hostname: str, folder_name: str, file_name: str) -> None: - """Initialise the reward component. - - :param node_hostname: Hostname of the node which contains the database file. - :type node_hostname: str - :param folder_name: folder which contains the database file. - :type folder_name: str - :param file_name: name of the database file. - :type file_name: str - """ - self.location_in_state = [ - "network", - "nodes", - node_hostname, - "file_system", - "folders", - folder_name, - "files", - file_name, - ] - def calculate(self, state: Dict, last_action_response: "AgentHistoryItem") -> float: """Calculate the reward for the current state. @@ -156,6 +141,17 @@ class DatabaseFileIntegrity(AbstractReward, identifier="DatabaseFileIntegrity"): :return: Reward value :rtype: float """ + self.location_in_state = [ + "network", + "nodes", + self.node_hostname, + "file_system", + "folders", + self.folder_name, + "files", + self.file_name, + ] + database_file_state = access_from_nested_dict(state, self.location_in_state) if database_file_state is NOT_PRESENT_IN_STATE: _LOGGER.debug( @@ -195,6 +191,12 @@ class DatabaseFileIntegrity(AbstractReward, identifier="DatabaseFileIntegrity"): class WebServer404Penalty(AbstractReward, identifier="WebServer404Penalty"): """Reward function component which penalises the agent when the web server returns a 404 error.""" + node_hostname: str + service_name: str + sticky: bool = True + location_in_state: List[str] = [""] + reward: float = 0.0 + class ConfigSchema(AbstractReward.ConfigSchema): """ConfigSchema for WebServer404Penalty.""" @@ -202,22 +204,6 @@ class WebServer404Penalty(AbstractReward, identifier="WebServer404Penalty"): service_name: str sticky: bool = True - def __init__(self, node_hostname: str, service_name: str, sticky: bool = True) -> None: - """Initialise the reward component. - - :param node_hostname: Hostname of the node which contains the web server service. - :type node_hostname: str - :param service_name: Name of the web server service. - :type service_name: str - :param sticky: If True, calculate the reward based on the most recent response status. If False, only calculate - the reward if there were any responses this timestep. - :type sticky: bool - """ - self.sticky: bool = sticky - self.reward: float = 0.0 - """Reward value calculated last time any responses were seen. Used for persisting sticky rewards.""" - self.location_in_state = ["network", "nodes", node_hostname, "services", service_name] - def calculate(self, state: Dict, last_action_response: "AgentHistoryItem") -> float: """Calculate the reward for the current state. @@ -228,6 +214,13 @@ class WebServer404Penalty(AbstractReward, identifier="WebServer404Penalty"): :return: Reward value :rtype: float """ + self.location_in_state = [ + "network", + "nodes", + self.node_hostname, + "services", + self.service_name, + ] web_service_state = access_from_nested_dict(state, self.location_in_state) # if webserver is no longer installed on the node, return 0 @@ -274,6 +267,7 @@ class WebServer404Penalty(AbstractReward, identifier="WebServer404Penalty"): class WebpageUnavailablePenalty(AbstractReward, identifier="WebpageUnavailablePenalty"): """Penalises the agent when the web browser fails to fetch a webpage.""" + node_hostname: str = "" sticky: bool = True reward: float = 0.0 @@ -287,22 +281,6 @@ class WebpageUnavailablePenalty(AbstractReward, identifier="WebpageUnavailablePe sticky: bool = True reward: float = 0.0 - # def __init__(self, node_hostname: str, sticky: bool = True) -> None: - # """ - # Initialise the reward component. - - # :param node_hostname: Hostname of the node which has the web browser. - # :type node_hostname: str - # :param sticky: If True, calculate the reward based on the most recent response status. If False, only calculate - # the reward if there were any responses this timestep. - # :type sticky: bool - # """ - # self._node: str = node_hostname - # self.location_in_state: List[str] = ["network", "nodes", node_hostname, "applications", "WebBrowser"] - # self.sticky: bool = sticky - # self.reward: float = 0.0 - # """Reward value calculated last time any responses were seen. Used for persisting sticky rewards.""" - def calculate(self, state: Dict, last_action_response: "AgentHistoryItem") -> float: """ Calculate the reward based on current simulation state, and the recent agent action. @@ -317,7 +295,13 @@ class WebpageUnavailablePenalty(AbstractReward, identifier="WebpageUnavailablePe :return: Reward value :rtype: float """ - self.location_in_state: List[str] = ["network", "nodes", self.node_hostname, "applications", "WebBrowser"] + self.location_in_state = [ + "network", + "nodes", + self.node_hostname, + "applications", + "WebBrowser", + ] web_browser_state = access_from_nested_dict(state, self.location_in_state) if web_browser_state is NOT_PRESENT_IN_STATE: @@ -371,6 +355,7 @@ class WebpageUnavailablePenalty(AbstractReward, identifier="WebpageUnavailablePe class GreenAdminDatabaseUnreachablePenalty(AbstractReward, identifier="GreenAdminDatabaseUnreachablePenalty"): """Penalises the agent when the green db clients fail to connect to the database.""" + node_hostname: str = "" _node: str = node_hostname sticky: bool = True @@ -382,22 +367,6 @@ class GreenAdminDatabaseUnreachablePenalty(AbstractReward, identifier="GreenAdmi node_hostname: str sticky: bool = True - # def __init__(self, node_hostname: str, sticky: bool = True) -> None: - # """ - # Initialise the reward component. - - # :param node_hostname: Hostname of the node where the database client sits. - # :type node_hostname: str - # :param sticky: If True, calculate the reward based on the most recent response status. If False, only calculate - # the reward if there were any responses this timestep. - # :type sticky: bool - # """ - # self._node: str = node_hostname - # self.location_in_state: List[str] = ["network", "nodes", node_hostname, "applications", "DatabaseClient"] - # self.sticky: bool = sticky - # self.reward: float = 0.0 - # """Reward value calculated last time any responses were seen. Used for persisting sticky rewards.""" - def calculate(self, state: Dict, last_action_response: "AgentHistoryItem") -> float: """ Calculate the reward based on current simulation state, and the recent agent action. @@ -449,6 +418,7 @@ class GreenAdminDatabaseUnreachablePenalty(AbstractReward, identifier="GreenAdmi class SharedReward(AbstractReward, identifier="SharedReward"): """Adds another agent's reward to the overall reward.""" + agent_name: str class ConfigSchema(AbstractReward.ConfigSchema): @@ -456,19 +426,6 @@ class SharedReward(AbstractReward, identifier="SharedReward"): agent_name: str - # def __init__(self, agent_name: Optional[str] = None) -> None: - # """ - # Initialise the shared reward. - - # The agent_name is a placeholder value. It starts off as none, but it must be set before this reward can work - # correctly. - - # :param agent_name: The name whose reward is an input - # :type agent_name: Optional[str] - # """ - # # self.agent_name = agent_name - # """Agent whose reward to track.""" - def default_callback(agent_name: str) -> Never: """ Default callback to prevent calling this reward until it's properly initialised. @@ -508,6 +465,7 @@ class SharedReward(AbstractReward, identifier="SharedReward"): class ActionPenalty(AbstractReward, identifier="ActionPenalty"): """Apply a negative reward when taking any action except DONOTHING.""" + action_penalty: float = -1.0 do_nothing_penalty: float = 0.0 @@ -517,21 +475,6 @@ class ActionPenalty(AbstractReward, identifier="ActionPenalty"): action_penalty: float = -1.0 do_nothing_penalty: float = 0.0 - # def __init__(self, action_penalty: float, do_nothing_penalty: float) -> None: - # """ - # Initialise the reward. - - # Reward or penalise agents for doing nothing or taking actions. - - # :param action_penalty: Reward to give agents for taking any action except DONOTHING - # :type action_penalty: float - # :param do_nothing_penalty: Reward to give agent for taking the DONOTHING action - # :type do_nothing_penalty: float - # """ - # super().__init__(action_penalty=action_penalty, do_nothing_penalty=do_nothing_penalty) - # self.action_penalty = action_penalty - # self.do_nothing_penalty = do_nothing_penalty - def calculate(self, state: Dict, last_action_response: "AgentHistoryItem") -> float: """Calculate the penalty to be applied. diff --git a/tests/unit_tests/_primaite/_game/_agent/test_sticky_rewards.py b/tests/unit_tests/_primaite/_game/_agent/test_sticky_rewards.py index 58f0fcc1..2ad1a322 100644 --- a/tests/unit_tests/_primaite/_game/_agent/test_sticky_rewards.py +++ b/tests/unit_tests/_primaite/_game/_agent/test_sticky_rewards.py @@ -11,7 +11,7 @@ from primaite.interface.request import RequestResponse class TestWebServer404PenaltySticky: def test_non_sticky(self): - reward = WebServer404Penalty("computer", "WebService", sticky=False) + reward = WebServer404Penalty(node_hostname="computer", service_name="WebService", sticky=False) # no response codes yet, reward is 0 codes = [] @@ -38,7 +38,7 @@ class TestWebServer404PenaltySticky: assert reward.calculate(state, last_action_response) == -1.0 def test_sticky(self): - reward = WebServer404Penalty("computer", "WebService", sticky=True) + reward = WebServer404Penalty(node_hostname="computer", service_name="WebService", sticky=True) # no response codes yet, reward is 0 codes = [] @@ -67,7 +67,7 @@ class TestWebServer404PenaltySticky: class TestWebpageUnavailabilitySticky: def test_non_sticky(self): - reward = WebpageUnavailablePenalty("computer", sticky=False) + reward = WebpageUnavailablePenalty(node_hostname="computer", sticky=False) # no response codes yet, reward is 0 action, params, request = "DO_NOTHING", {}, ["DONOTHING"] @@ -127,7 +127,7 @@ class TestWebpageUnavailabilitySticky: assert reward.calculate(state, last_action_response) == -1.0 def test_sticky(self): - reward = WebpageUnavailablePenalty("computer", sticky=True) + reward = WebpageUnavailablePenalty(node_hostname="computer", sticky=True) # no response codes yet, reward is 0 action, params, request = "DO_NOTHING", {}, ["DONOTHING"] @@ -188,7 +188,7 @@ class TestWebpageUnavailabilitySticky: class TestGreenAdminDatabaseUnreachableSticky: def test_non_sticky(self): - reward = GreenAdminDatabaseUnreachablePenalty("computer", sticky=False) + reward = GreenAdminDatabaseUnreachablePenalty(node_hostname="computer", sticky=False) # no response codes yet, reward is 0 action, params, request = "DO_NOTHING", {}, ["DONOTHING"] @@ -244,7 +244,7 @@ class TestGreenAdminDatabaseUnreachableSticky: assert reward.calculate(state, last_action_response) == -1.0 def test_sticky(self): - reward = GreenAdminDatabaseUnreachablePenalty("computer", sticky=True) + reward = GreenAdminDatabaseUnreachablePenalty(node_hostname="computer", sticky=True) # no response codes yet, reward is 0 action, params, request = "DO_NOTHING", {}, ["DONOTHING"] From 318f8926f0f7f0c85f3ce07bf551ca5ada88bbf9 Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Tue, 22 Oct 2024 12:14:30 +0100 Subject: [PATCH 13/58] #2913: Fix remaining test errors. --- src/primaite/game/agent/rewards.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/primaite/game/agent/rewards.py b/src/primaite/game/agent/rewards.py index 1f870e83..2386bed5 100644 --- a/src/primaite/game/agent/rewards.py +++ b/src/primaite/game/agent/rewards.py @@ -272,7 +272,6 @@ class WebpageUnavailablePenalty(AbstractReward, identifier="WebpageUnavailablePe sticky: bool = True reward: float = 0.0 location_in_state: List[str] = [""] - _node: str = node_hostname class ConfigSchema(AbstractReward.ConfigSchema): """ConfigSchema for WebpageUnavailablePenalty.""" @@ -311,7 +310,7 @@ class WebpageUnavailablePenalty(AbstractReward, identifier="WebpageUnavailablePe request_attempted = last_action_response.request == [ "network", "node", - self._node, + self.node_hostname, "application", "WebBrowser", "execute", @@ -326,7 +325,7 @@ class WebpageUnavailablePenalty(AbstractReward, identifier="WebpageUnavailablePe elif web_browser_state is NOT_PRESENT_IN_STATE or not web_browser_state["history"]: _LOGGER.debug( "Web browser reward could not be calculated because the web browser history on node", - f"{self._node} was not reported in the simulation state. Returning 0.0", + f"{self.node_hostname} was not reported in the simulation state. Returning 0.0", ) self.reward = 0.0 else: @@ -357,7 +356,6 @@ class GreenAdminDatabaseUnreachablePenalty(AbstractReward, identifier="GreenAdmi """Penalises the agent when the green db clients fail to connect to the database.""" node_hostname: str = "" - _node: str = node_hostname sticky: bool = True reward: float = 0.0 @@ -385,7 +383,7 @@ class GreenAdminDatabaseUnreachablePenalty(AbstractReward, identifier="GreenAdmi request_attempted = last_action_response.request == [ "network", "node", - self._node, + self.node_hostname, "application", "DatabaseClient", "execute", From 37bdbaf0d1ebb624b0b279d6a2ba8ba7cfd142f7 Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Tue, 22 Oct 2024 16:15:04 +0100 Subject: [PATCH 14/58] #2913: Fix JSON breakage and old-style PORTS and PROTOCOL usage. --- .../Command-&-Control-E2E-Demonstration.ipynb | 10 +++--- .../create-simulation_demo.ipynb | 31 ++++++++++--------- .../network_simulator_demo.ipynb | 14 ++++----- 3 files changed, 29 insertions(+), 26 deletions(-) diff --git a/src/primaite/notebooks/Command-&-Control-E2E-Demonstration.ipynb b/src/primaite/notebooks/Command-&-Control-E2E-Demonstration.ipynb index 6e6819fa..368dccf8 100644 --- a/src/primaite/notebooks/Command-&-Control-E2E-Demonstration.ipynb +++ b/src/primaite/notebooks/Command-&-Control-E2E-Demonstration.ipynb @@ -1781,9 +1781,11 @@ "outputs": [], "source": [ "from primaite.simulator.network.transmission.network_layer import IPProtocol\n", - "from primaite.simulator.network.transmission.transport_layer import Port\n", + "from primaite.utils.validation.ip_protocol import PROTOCOL_LOOKUP\n", + "from primaite.utils.validation.port import PORT_LOOKUP\n", + "\n", "# As we're configuring via the PrimAITE API we need to pass the actual IPProtocol/Port (Agents leverage the simulation via the game layer and thus can pass strings).\n", - "c2_beacon.configure(c2_server_ip_address=\"192.168.10.21\", masquerade_protocol=IPProtocol["UDP"], masquerade_port=Port["DNS"])\n", + "c2_beacon.configure(c2_server_ip_address=\"192.168.10.21\", masquerade_protocol=PROTOCOL_LOOKUP[\"UDP\"], masquerade_port=PORT_LOOKUP[\"DNS\"])\n", "c2_beacon.establish()\n", "c2_beacon.show()" ] @@ -1804,7 +1806,7 @@ ], "metadata": { "kernelspec": { - "display_name": "venv", + "display_name": ".venv", "language": "python", "name": "python3" }, @@ -1818,7 +1820,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.11" + "version": "3.10.12" } }, "nbformat": 4, diff --git a/src/primaite/simulator/_package_data/create-simulation_demo.ipynb b/src/primaite/simulator/_package_data/create-simulation_demo.ipynb index f573f251..7ce8baaf 100644 --- a/src/primaite/simulator/_package_data/create-simulation_demo.ipynb +++ b/src/primaite/simulator/_package_data/create-simulation_demo.ipynb @@ -20,7 +20,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -56,7 +56,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -66,7 +66,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ @@ -85,7 +85,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -121,7 +121,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ @@ -132,7 +132,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "metadata": {}, "outputs": [], "source": [ @@ -159,16 +159,17 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "from pathlib import Path\n", "from primaite.simulator.system.applications.application import Application, ApplicationOperatingState\n", "from primaite.simulator.system.software import SoftwareHealthState, SoftwareCriticality\n", - "from primaite.simulator.network.transmission.transport_layer import Port\n", - "from primaite.simulator.network.transmission.network_layer import IPProtocol\n", "from primaite.simulator.file_system.file_system import FileSystem\n", + "from primaite.utils.validation.ip_protocol import PROTOCOL_LOOKUP\n", + "from primaite.utils.validation.port import PORT_LOOKUP\n", + "\n", "\n", "# no applications exist yet so we will create our own.\n", "class MSPaint(Application, identifier=\"MSPaint\"):\n", @@ -178,16 +179,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ - "mspaint = MSPaint(name = \"mspaint\", health_state_actual=SoftwareHealthState.GOOD, health_state_visible=SoftwareHealthState.GOOD, criticality=SoftwareCriticality.MEDIUM, port=Port["HTTP"], protocol = IPProtocol["NONE"],operating_state=ApplicationOperatingState.RUNNING,execution_control_status='manual', file_system=FileSystem(sys_log=SysLog(hostname=\"Test\"), sim_root=Path(__name__).parent),)" + "mspaint = MSPaint(name = \"mspaint\", health_state_actual=SoftwareHealthState.GOOD, health_state_visible=SoftwareHealthState.GOOD, criticality=SoftwareCriticality.MEDIUM, port=PORT_LOOKUP[\"HTTP\"], protocol = PROTOCOL_LOOKUP[\"NONE\"],operating_state=ApplicationOperatingState.RUNNING,execution_control_status='manual', file_system=FileSystem(sys_log=SysLog(hostname=\"Test\"), sim_root=Path(__name__).parent),)" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "metadata": {}, "outputs": [], "source": [ @@ -203,7 +204,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "metadata": {}, "outputs": [], "source": [ @@ -212,7 +213,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 14, "metadata": {}, "outputs": [], "source": [ @@ -249,7 +250,7 @@ ], "metadata": { "kernelspec": { - "display_name": "venv", + "display_name": ".venv", "language": "python", "name": "python3" }, diff --git a/src/primaite/simulator/_package_data/network_simulator_demo.ipynb b/src/primaite/simulator/_package_data/network_simulator_demo.ipynb index 2d5b4772..b09baa85 100644 --- a/src/primaite/simulator/_package_data/network_simulator_demo.ipynb +++ b/src/primaite/simulator/_package_data/network_simulator_demo.ipynb @@ -63,7 +63,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "3", "metadata": { "tags": [] @@ -75,7 +75,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "4", "metadata": { "tags": [] @@ -532,12 +532,12 @@ }, "outputs": [], "source": [ - "from primaite.simulator.network.transmission.network_layer import IPProtocol\n", - "from primaite.simulator.network.transmission.transport_layer import Port\n", "from primaite.simulator.network.hardware.nodes.network.router import ACLAction\n", + "from primaite.utils.validation.ip_protocol import PROTOCOL_LOOKUP\n", + "\n", "network.get_node_by_hostname(\"router_1\").acl.add_rule(\n", " action=ACLAction.DENY,\n", - " protocol=IPProtocol["ICMP"],\n", + " protocol=PROTOCOL_LOOKUP[\"ICMP\"],\n", " src_ip_address=\"192.168.10.22\",\n", " position=1\n", ")" @@ -650,7 +650,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": ".venv", "language": "python", "name": "python3" }, @@ -664,7 +664,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.11" + "version": "3.10.12" } }, "nbformat": 4, From c3f266e40116514cae6d842351524b42927e43d9 Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Tue, 22 Oct 2024 16:26:57 +0100 Subject: [PATCH 15/58] #2913: Remove unneeded import and pre-commit changes. --- .../Command-&-Control-E2E-Demonstration.ipynb | 1 - .../create-simulation_demo.ipynb | 22 +++++++++---------- .../network_simulator_demo.ipynb | 4 ++-- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/primaite/notebooks/Command-&-Control-E2E-Demonstration.ipynb b/src/primaite/notebooks/Command-&-Control-E2E-Demonstration.ipynb index 368dccf8..d2972fa9 100644 --- a/src/primaite/notebooks/Command-&-Control-E2E-Demonstration.ipynb +++ b/src/primaite/notebooks/Command-&-Control-E2E-Demonstration.ipynb @@ -1780,7 +1780,6 @@ "metadata": {}, "outputs": [], "source": [ - "from primaite.simulator.network.transmission.network_layer import IPProtocol\n", "from primaite.utils.validation.ip_protocol import PROTOCOL_LOOKUP\n", "from primaite.utils.validation.port import PORT_LOOKUP\n", "\n", diff --git a/src/primaite/simulator/_package_data/create-simulation_demo.ipynb b/src/primaite/simulator/_package_data/create-simulation_demo.ipynb index 7ce8baaf..117ea019 100644 --- a/src/primaite/simulator/_package_data/create-simulation_demo.ipynb +++ b/src/primaite/simulator/_package_data/create-simulation_demo.ipynb @@ -20,7 +20,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -56,7 +56,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -66,7 +66,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -85,7 +85,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -121,7 +121,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -132,7 +132,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -159,7 +159,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -179,7 +179,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -188,7 +188,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -204,7 +204,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -213,7 +213,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ diff --git a/src/primaite/simulator/_package_data/network_simulator_demo.ipynb b/src/primaite/simulator/_package_data/network_simulator_demo.ipynb index b09baa85..4b620eee 100644 --- a/src/primaite/simulator/_package_data/network_simulator_demo.ipynb +++ b/src/primaite/simulator/_package_data/network_simulator_demo.ipynb @@ -63,7 +63,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "3", "metadata": { "tags": [] @@ -75,7 +75,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "4", "metadata": { "tags": [] From 85216bec942a472f9007dba41e1be2a2c0846456 Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Tue, 22 Oct 2024 16:48:30 +0100 Subject: [PATCH 16/58] #2913: Rename notebook to replace '&'. --- ...stration.ipynb => Command-and-Control-E2E-Demonstration.ipynb} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/primaite/notebooks/{Command-&-Control-E2E-Demonstration.ipynb => Command-and-Control-E2E-Demonstration.ipynb} (100%) diff --git a/src/primaite/notebooks/Command-&-Control-E2E-Demonstration.ipynb b/src/primaite/notebooks/Command-and-Control-E2E-Demonstration.ipynb similarity index 100% rename from src/primaite/notebooks/Command-&-Control-E2E-Demonstration.ipynb rename to src/primaite/notebooks/Command-and-Control-E2E-Demonstration.ipynb From 518b934e09086d7dcc1c9ebc7e0b763dbbf85b5e Mon Sep 17 00:00:00 2001 From: Charlie Crane Date: Tue, 22 Oct 2024 17:02:54 +0100 Subject: [PATCH 17/58] #2912 - Corrections to some actions & fixing some linting. TODO: Action Manager errors --- src/primaite/game/agent/actions/acl.py | 38 ++++---- src/primaite/game/agent/actions/config.py | 2 +- src/primaite/game/agent/actions/manager.py | 14 +-- src/primaite/game/agent/actions/node.py | 97 ++++++++++++++++++- src/primaite/game/agent/actions/service.py | 7 +- src/primaite/game/agent/actions/session.py | 7 +- src/primaite/notebooks/Action-masking.ipynb | 7 +- .../Command-&-Control-E2E-Demonstration.ipynb | 4 +- .../notebooks/Training-an-SB3-Agent.ipynb | 4 +- .../create-simulation_demo.ipynb | 6 +- .../network_simulator_demo.ipynb | 4 +- 11 files changed, 149 insertions(+), 41 deletions(-) diff --git a/src/primaite/game/agent/actions/acl.py b/src/primaite/game/agent/actions/acl.py index 1048dc1e..cc89bfba 100644 --- a/src/primaite/game/agent/actions/acl.py +++ b/src/primaite/game/agent/actions/acl.py @@ -3,10 +3,16 @@ from typing import Dict, List, Literal from pydantic import BaseModel, Field, field_validator, ValidationInfo -from primaite.game.agent.actions.manager import AbstractAction +from primaite.game.agent.actions.manager import AbstractAction, ActionManager from primaite.interface.request import RequestFormat -__all__ = ("RouterACLAddRuleAction", "RouterACLRemoveRuleAction", "FirewallACLAddRuleAction", "FirewallACLRemoveRuleAction") +__all__ = ( + "RouterACLAddRuleAction", + "RouterACLRemoveRuleAction", + "FirewallACLAddRuleAction", + "FirewallACLRemoveRuleAction", +) + class RouterACLAddRuleAction(AbstractAction, identifier="router_acl_add_rule"): """Action which adds a rule to a router's ACL.""" @@ -23,8 +29,7 @@ class RouterACLAddRuleAction(AbstractAction, identifier="router_acl_add_rule"): dest_ip_id: int dest_wildcard_id: int dest_port_id: int - protocol_id: int - + protocol_name: str class ACLRuleOptions(BaseModel): """Validator for ACL_ADD_RULE options.""" @@ -68,10 +73,7 @@ class RouterACLAddRuleAction(AbstractAction, identifier="router_acl_add_rule"): return v @classmethod - def form_request( - cls, - config: ConfigSchema - ) -> List[str]: + def form_request(cls, config: ConfigSchema) -> List[str]: """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" # Validate incoming data. parsed_options = RouterACLAddRuleAction.ACLRuleOptions( @@ -84,7 +86,7 @@ class RouterACLAddRuleAction(AbstractAction, identifier="router_acl_add_rule"): dest_wildcard_id=config.dest_wildcard_id, source_port_id=config.source_port_id, dest_port_id=config.dest_port_id, - protocol_id=config.protocol_id, + protocol=config.protocol_name, ) if parsed_options.permission == 1: permission_str = "PERMIT" @@ -96,40 +98,40 @@ class RouterACLAddRuleAction(AbstractAction, identifier="router_acl_add_rule"): if parsed_options.protocol_id == 1: protocol = "ALL" else: - protocol = self.manager.get_internet_protocol_by_idx(parsed_options.protocol_id - 2) + protocol = cls.manager.get_internet_protocol_by_idx(parsed_options.protocol_id - 2) # subtract 2 to account for UNUSED=0 and ALL=1. if parsed_options.source_ip_id == 1: src_ip = "ALL" else: - src_ip = self.manager.get_ip_address_by_idx(parsed_options.source_ip_id - 2) + src_ip = cls.manager.get_ip_address_by_idx(parsed_options.source_ip_id - 2) # subtract 2 to account for UNUSED=0, and ALL=1 - src_wildcard = self.manager.get_wildcard_by_idx(parsed_options.source_wildcard_id) + src_wildcard = cls.manager.get_wildcard_by_idx(parsed_options.source_wildcard_id) if parsed_options.source_port_id == 1: src_port = "ALL" else: - src_port = self.manager.get_port_by_idx(parsed_options.source_port_id - 2) + src_port = cls.manager.get_port_by_idx(parsed_options.source_port_id - 2) # subtract 2 to account for UNUSED=0, and ALL=1 if parsed_options.dest_ip_id == 1: dst_ip = "ALL" else: - dst_ip = self.manager.get_ip_address_by_idx(parsed_options.dest_ip_id - 2) + dst_ip = cls.manager.get_ip_address_by_idx(parsed_options.dest_ip_id - 2) # subtract 2 to account for UNUSED=0, and ALL=1 - dst_wildcard = self.manager.get_wildcard_by_idx(parsed_options.dest_wildcard_id) + dst_wildcard = cls.manager.get_wildcard_by_idx(parsed_options.dest_wildcard_id) if parsed_options.dest_port_id == 1: dst_port = "ALL" else: - dst_port = self.manager.get_port_by_idx(parsed_options.dest_port_id - 2) + dst_port = cls.manager.get_port_by_idx(parsed_options.dest_port_id - 2) # subtract 2 to account for UNUSED=0, and ALL=1 return [ "network", "node", - target_router, + config.target_router, "acl", "add_rule", permission_str, @@ -140,7 +142,7 @@ class RouterACLAddRuleAction(AbstractAction, identifier="router_acl_add_rule"): str(dst_ip), dst_wildcard, dst_port, - position, + config.position, ] diff --git a/src/primaite/game/agent/actions/config.py b/src/primaite/game/agent/actions/config.py index e92d443b..582e8ec7 100644 --- a/src/primaite/game/agent/actions/config.py +++ b/src/primaite/game/agent/actions/config.py @@ -4,7 +4,7 @@ from typing import Dict, List, Optional, Union from pydantic import BaseModel, ConfigDict, Field, field_validator, ValidationInfo -from primaite.game.agent.actions.manager import AbstractAction +from primaite.game.agent.actions.manager import AbstractAction, ActionManager from primaite.interface.request import RequestFormat __all__ = ( diff --git a/src/primaite/game/agent/actions/manager.py b/src/primaite/game/agent/actions/manager.py index 2f47ea7c..7677b39a 100644 --- a/src/primaite/game/agent/actions/manager.py +++ b/src/primaite/game/agent/actions/manager.py @@ -87,6 +87,8 @@ class ActionManager: # ip_list: List[str] = [], # to allow us to map an index to an ip address. # wildcard_list: List[str] = [], # to allow mapping from wildcard index to act_map: Optional[Dict[int, Dict]] = None, # allows restricting set of possible actions + *args, + **kwargs, ) -> None: """Init method for ActionManager. @@ -116,27 +118,27 @@ class ActionManager: :param act_map: Action map which maps integers to actions. Used for restricting the set of possible actions. :type act_map: Optional[Dict[int, Dict]] """ - self.node_names: List[str] = [n["node_name"] for n in nodes] + # self.node_names: List[str] = [n["node_name"] for n in nodes] """List of node names in this action space. The list order is the mapping between node index and node name.""" - self.application_names: List[List[str]] = [] + # self.application_names: List[List[str]] = [] """ List of applications per node. The list order gives the two-index mapping between (node_id, app_id) to app name. The first index corresponds to node id, the second index is the app id on that particular node. For instance, self.application_names[0][2] is the name of the third application on the first node. """ - self.service_names: List[List[str]] = [] + # self.service_names: List[List[str]] = [] """ List of services per node. The list order gives the two-index mapping between (node_id, svc_id) to svc name. The first index corresponds to node id, the second index is the service id on that particular node. For instance, self.service_names[0][2] is the name of the third service on the first node. """ - self.folder_names: List[List[str]] = [] + # self.folder_names: List[List[str]] = [] """ List of folders per node. The list order gives the two-index mapping between (node_id, folder_id) to folder name. The first index corresponds to node id, the second index is the folder id on that particular node. For instance, self.folder_names[0][2] is the name of the third folder on the first node. """ - self.file_names: List[List[List[str]]] = [] + # self.file_names: List[List[List[str]]] = [] """ List of files per folder per node. The list order gives the three-index mapping between (node_id, folder_id, file_id) to file name. The first index corresponds to node id, the second index is the @@ -203,7 +205,7 @@ class ActionManager: # and `options` is an optional dict of options to pass to the init method of the action class act_type = act_spec.get("type") act_options = act_spec.get("options", {}) - self.actions[act_type] = self.act_class_identifiers[act_type](self, **global_action_args, **act_options) + # self.actions[act_type] = self.act_class_identifiers[act_type](self, **global_action_args, **act_options) self.action_map: Dict[int, Tuple[str, Dict]] = {} """ diff --git a/src/primaite/game/agent/actions/node.py b/src/primaite/game/agent/actions/node.py index 011ff4dc..f95ba6df 100644 --- a/src/primaite/game/agent/actions/node.py +++ b/src/primaite/game/agent/actions/node.py @@ -1,5 +1,6 @@ # © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK -from typing import ClassVar +from abc import abstractmethod +from typing import ClassVar, List, Optional, Union from primaite.game.agent.actions.manager import AbstractAction from primaite.interface.request import RequestFormat @@ -61,3 +62,97 @@ class NodeResetAction(NodeAbstractAction, identifier="node_reset"): """Configuration schema for NodeResetAction.""" verb: str = "reset" + + +class NodeNMAPAbstractAction(AbstractAction, identifier="node_nmap_abstract_action"): + """Base class for NodeNMAP actions.""" + + class ConfigSchema(AbstractAction.ConfigSchema): + """Base Configuration Schema for NodeNMAP actions.""" + + target_ip_address: Union[str, List[str]] + show: bool = False + node_name: str + + @classmethod + @abstractmethod + def form_request(cls, config: ConfigSchema) -> RequestFormat: + pass + + +class NodeNMAPPingScanAction(NodeNMAPAbstractAction, identifier="node_nmap_ping_scan"): + class ConfigSchema(NodeNMAPAbstractAction.ConfigSchema): + pass + + @classmethod + def form_request(cls, config: ConfigSchema) -> List[str]: # noqa + """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" + return [ + "network", + "node", + config.node_name, + "application", + "NMAP", + "ping_scan", + {"target_ip_address": config.target_ip_address, "show": config.show}, + ] + + +class NodeNMAPPortScanAction(NodeNMAPAbstractAction, identifier="node_nmap_port_scan"): + """Action which performs an NMAP port scan.""" + + class ConfigSchema(AbstractAction.ConfigSchema): + target_protocol: Optional[Union[str, List[str]]] = (None,) + target_port: Optional[Union[str, List[str]]] = (None,) + show: Optional[bool] = (False,) + + @classmethod + def form_request( + cls, + config: ConfigSchema, + ) -> List[str]: # noqa + """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" + return [ + "network", + "node", + config.source_node, + "application", + "NMAP", + "port_scan", + { + "target_ip_address": config.target_ip_address, + "target_port": config.target_port, + "target_protocol": config.target_protocol, + "show": config.show, + }, + ] + + +class NodeNetworkServiceReconAction(NodeNMAPAbstractAction, identifier="node_network_service_recon"): + """Action which performs an NMAP network service recon (ping scan followed by port scan).""" + + class ConfigSchema(AbstractAction.ConfigSchema): + target_protocol: Optional[Union[str, List[str]]] = (None,) + target_port: Optional[Union[str, List[str]]] = (None,) + show: Optional[bool] = (False,) + + @classmethod + def form_request( + cls, + config: ConfigSchema, + ) -> List[str]: # noqa + """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" + return [ + "network", + "node", + config.source_node, + "application", + "NMAP", + "network_service_recon", + { + "target_ip_address": config.target_ip_address, + "target_port": config.target_port, + "target_protocol": config.target_protocol, + "show": config.show, + }, + ] diff --git a/src/primaite/game/agent/actions/service.py b/src/primaite/game/agent/actions/service.py index cf277b5d..bccfaba2 100644 --- a/src/primaite/game/agent/actions/service.py +++ b/src/primaite/game/agent/actions/service.py @@ -18,6 +18,11 @@ __all__ = ( class NodeServiceAbstractAction(AbstractAction, identifier="node_service_abstract"): + """Abstract Action for Node Service related actions. + + Any actions which use node_name and service_name can inherit from this class. + """ + class ConfigSchema(AbstractAction.ConfigSchema): node_name: str service_name: str @@ -34,7 +39,7 @@ class NodeServiceScanAction(NodeServiceAbstractAction, identifier="node_service_ """Action which scans a service.""" class ConfigSchema(NodeServiceAbstractAction.ConfigSchema): - """Configuration Schema for NodeServiceScanAction""" + """Configuration Schema for NodeServiceScanAction.""" verb: str = "scan" diff --git a/src/primaite/game/agent/actions/session.py b/src/primaite/game/agent/actions/session.py index eb035ff3..f77a85b1 100644 --- a/src/primaite/game/agent/actions/session.py +++ b/src/primaite/game/agent/actions/session.py @@ -19,8 +19,11 @@ class NodeSessionAbstractAction(AbstractAction, identifier="node_session_abstrac @classmethod @abstractmethod def form_request(cls, config: ConfigSchema) -> RequestFormat: - """Abstract method. Should return the action formatted as a request which - can be ingested by the PrimAITE simulation.""" + """ + Abstract method for request forming. + + Should return the action formatted as a request which can be ingested by the PrimAITE simulation. + """ pass diff --git a/src/primaite/notebooks/Action-masking.ipynb b/src/primaite/notebooks/Action-masking.ipynb index ba70f2b4..d22e171d 100644 --- a/src/primaite/notebooks/Action-masking.ipynb +++ b/src/primaite/notebooks/Action-masking.ipynb @@ -19,7 +19,8 @@ "source": [ "from primaite.session.environment import PrimaiteGymEnv\n", "from primaite.config.load import data_manipulation_config_path\n", - "from prettytable import PrettyTable\n" + "from prettytable import PrettyTable\n", + "UDP=\"UDP\"" ] }, { @@ -195,7 +196,7 @@ ], "metadata": { "kernelspec": { - "display_name": "venv", + "display_name": ".venv", "language": "python", "name": "python3" }, @@ -209,7 +210,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.12" + "version": "3.10.11" } }, "nbformat": 4, diff --git a/src/primaite/notebooks/Command-&-Control-E2E-Demonstration.ipynb b/src/primaite/notebooks/Command-&-Control-E2E-Demonstration.ipynb index 6e6819fa..a697ca3e 100644 --- a/src/primaite/notebooks/Command-&-Control-E2E-Demonstration.ipynb +++ b/src/primaite/notebooks/Command-&-Control-E2E-Demonstration.ipynb @@ -1783,7 +1783,7 @@ "from primaite.simulator.network.transmission.network_layer import IPProtocol\n", "from primaite.simulator.network.transmission.transport_layer import Port\n", "# As we're configuring via the PrimAITE API we need to pass the actual IPProtocol/Port (Agents leverage the simulation via the game layer and thus can pass strings).\n", - "c2_beacon.configure(c2_server_ip_address=\"192.168.10.21\", masquerade_protocol=IPProtocol["UDP"], masquerade_port=Port["DNS"])\n", + "c2_beacon.configure(c2_server_ip_address=\"192.168.10.21\", masquerade_protocol=IPProtocol[\"UDP\"], masquerade_port=Port[\"DNS\"])\n", "c2_beacon.establish()\n", "c2_beacon.show()" ] @@ -1804,7 +1804,7 @@ ], "metadata": { "kernelspec": { - "display_name": "venv", + "display_name": ".venv", "language": "python", "name": "python3" }, diff --git a/src/primaite/notebooks/Training-an-SB3-Agent.ipynb b/src/primaite/notebooks/Training-an-SB3-Agent.ipynb index 892736fe..5255b0ad 100644 --- a/src/primaite/notebooks/Training-an-SB3-Agent.ipynb +++ b/src/primaite/notebooks/Training-an-SB3-Agent.ipynb @@ -168,7 +168,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": ".venv", "language": "python", "name": "python3" }, @@ -182,7 +182,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.8" + "version": "3.10.11" } }, "nbformat": 4, diff --git a/src/primaite/simulator/_package_data/create-simulation_demo.ipynb b/src/primaite/simulator/_package_data/create-simulation_demo.ipynb index f573f251..30417b84 100644 --- a/src/primaite/simulator/_package_data/create-simulation_demo.ipynb +++ b/src/primaite/simulator/_package_data/create-simulation_demo.ipynb @@ -182,7 +182,7 @@ "metadata": {}, "outputs": [], "source": [ - "mspaint = MSPaint(name = \"mspaint\", health_state_actual=SoftwareHealthState.GOOD, health_state_visible=SoftwareHealthState.GOOD, criticality=SoftwareCriticality.MEDIUM, port=Port["HTTP"], protocol = IPProtocol["NONE"],operating_state=ApplicationOperatingState.RUNNING,execution_control_status='manual', file_system=FileSystem(sys_log=SysLog(hostname=\"Test\"), sim_root=Path(__name__).parent),)" + "mspaint = MSPaint(name = \"mspaint\", health_state_actual=SoftwareHealthState.GOOD, health_state_visible=SoftwareHealthState.GOOD, criticality=SoftwareCriticality.MEDIUM, port=Port[\"HTTP\"], protocol = IPProtocol[\"NONE\"],operating_state=ApplicationOperatingState.RUNNING,execution_control_status='manual', file_system=FileSystem(sys_log=SysLog(hostname=\"Test\"), sim_root=Path(__name__).parent),)" ] }, { @@ -249,7 +249,7 @@ ], "metadata": { "kernelspec": { - "display_name": "venv", + "display_name": ".venv", "language": "python", "name": "python3" }, @@ -263,7 +263,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.12" + "version": "3.10.11" } }, "nbformat": 4, diff --git a/src/primaite/simulator/_package_data/network_simulator_demo.ipynb b/src/primaite/simulator/_package_data/network_simulator_demo.ipynb index 2d5b4772..8406dbdf 100644 --- a/src/primaite/simulator/_package_data/network_simulator_demo.ipynb +++ b/src/primaite/simulator/_package_data/network_simulator_demo.ipynb @@ -537,7 +537,7 @@ "from primaite.simulator.network.hardware.nodes.network.router import ACLAction\n", "network.get_node_by_hostname(\"router_1\").acl.add_rule(\n", " action=ACLAction.DENY,\n", - " protocol=IPProtocol["ICMP"],\n", + " protocol=IPProtocol[\"ICMP\"],\n", " src_ip_address=\"192.168.10.22\",\n", " position=1\n", ")" @@ -650,7 +650,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": ".venv", "language": "python", "name": "python3" }, From 5cd629a82163c486e9a9c33ec503265c11adfc28 Mon Sep 17 00:00:00 2001 From: Charlie Crane Date: Wed, 23 Oct 2024 18:45:57 +0100 Subject: [PATCH 18/58] #2912 - Fixed actionmanager issue and moved abstractaction to solve import error --- .../scenario_with_placeholders/scenario.yaml | 44 ++++++++--------- src/primaite/game/agent/actions/__init__.py | 2 + src/primaite/game/agent/actions/abstract.py | 48 ++++++++++++++++++ .../game/agent/actions/application.py | 2 +- src/primaite/game/agent/actions/manager.py | 49 ++++--------------- src/primaite/notebooks/Action-masking.ipynb | 3 +- 6 files changed, 83 insertions(+), 65 deletions(-) create mode 100644 src/primaite/game/agent/actions/abstract.py diff --git a/src/primaite/config/_package_data/scenario_with_placeholders/scenario.yaml b/src/primaite/config/_package_data/scenario_with_placeholders/scenario.yaml index dfd200f3..8c83bf79 100644 --- a/src/primaite/config/_package_data/scenario_with_placeholders/scenario.yaml +++ b/src/primaite/config/_package_data/scenario_with_placeholders/scenario.yaml @@ -55,50 +55,50 @@ agents: action_space: action_list: - - type: DONOTHING - - type: NODE_SHUTDOWN - - type: NODE_STARTUP - - type: HOST_NIC_ENABLE - - type: HOST_NIC_DISABLE + - type: do_nothing + - type: node_shutdown + - type: node_startup + - type: host_nic_enable + - type: host_nic_enable action_map: 0: - action: DONOTHING + action: do_nothing options: {} 1: - action: NODE_SHUTDOWN + action: node_shutdown options: - node_id: 0 + node_name: client_1 2: - action: NODE_SHUTDOWN + action: node_shutdown options: - node_id: 1 + node_name: server 3: - action: NODE_STARTUP + action: node_startup options: - node_id: 0 + node_name: client_1 4: - action: NODE_STARTUP + action: node_startup options: - node_id: 1 + node_name: server 5: - action: HOST_NIC_DISABLE + action: host_nic_disable options: - node_id: 0 + node_name: client_1 nic_id: 0 6: - action: HOST_NIC_DISABLE + action: host_nic_disable options: - node_id: 1 + node_name: server nic_id: 0 7: - action: HOST_NIC_ENABLE + action: host_nic_enable options: - node_id: 0 + node_name: client_1 nic_id: 0 8: - action: HOST_NIC_ENABLE + action: host_nic_enable options: - node_id: 1 + node_name: server nic_id: 0 options: nodes: diff --git a/src/primaite/game/agent/actions/__init__.py b/src/primaite/game/agent/actions/__init__.py index 428c6c58..3540b128 100644 --- a/src/primaite/game/agent/actions/__init__.py +++ b/src/primaite/game/agent/actions/__init__.py @@ -1,6 +1,7 @@ # © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from primaite.game.agent.actions import ( + abstract, acl, application, config, @@ -17,6 +18,7 @@ from primaite.game.agent.actions.manager import ActionManager __all__ = ( "acl", + "abstract", "application", "config", "file", diff --git a/src/primaite/game/agent/actions/abstract.py b/src/primaite/game/agent/actions/abstract.py new file mode 100644 index 00000000..5250e532 --- /dev/null +++ b/src/primaite/game/agent/actions/abstract.py @@ -0,0 +1,48 @@ +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +from __future__ import annotations +from abc import ABC +from typing import Any, ClassVar, Dict, Type + +from pydantic import BaseModel, ConfigDict + +from primaite.interface.request import RequestFormat + +class AbstractAction(BaseModel): + """Base class for actions.""" + + # notes: + # we actually don't need to hold any state in actions, so there's no need to define any __init__ logic. + # all the init methods in the old actions are just used for holding a verb and shape, which are not really used. + # the config schema should be used to the actual parameters for formatting the action itself. + # (therefore there's no need for creating action instances, just the action class contains logic for converting + # CAOS actions to requests for simulator. Similar to the network node adder, that class also doesn't need to be + # instantiated.) + class ConfigSchema(BaseModel, ABC): + """Base configuration schema for Actions.""" + + model_config = ConfigDict(extra="forbid") + type: str + + _registry: ClassVar[Dict[str, Type[AbstractAction]]] = {} + + def __init_subclass__(cls, identifier: str, **kwargs: Any) -> None: + super().__init_subclass__(**kwargs) + if identifier in cls._registry: + raise ValueError(f"Cannot create new action under reserved name {identifier}") + cls._registry[identifier] = cls + + @classmethod + def form_request(cls, config: ConfigSchema) -> RequestFormat: + """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" + return [] + + @classmethod + def from_config(cls, config: Dict) -> "AbstractAction": + """Create an action component from a config dictionary""" + + type_id = config.get("type") + + if type_id in cls._registry: + return cls(type=type_id, model_config=config) + else: + return [] \ No newline at end of file diff --git a/src/primaite/game/agent/actions/application.py b/src/primaite/game/agent/actions/application.py index 3a254d57..e0496dc7 100644 --- a/src/primaite/game/agent/actions/application.py +++ b/src/primaite/game/agent/actions/application.py @@ -1,7 +1,7 @@ # © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import ClassVar -from primaite.game.agent.actions.manager import AbstractAction +from primaite.game.agent.actions.abstract import AbstractAction from primaite.interface.request import RequestFormat __all__ = ( diff --git a/src/primaite/game/agent/actions/manager.py b/src/primaite/game/agent/actions/manager.py index 7677b39a..98ffbd00 100644 --- a/src/primaite/game/agent/actions/manager.py +++ b/src/primaite/game/agent/actions/manager.py @@ -14,48 +14,17 @@ agents: from __future__ import annotations import itertools -from abc import ABC -from typing import Any, ClassVar, Dict, List, Literal, Optional, Tuple, Type +from typing import Dict, List, Literal, Optional, Tuple from gymnasium import spaces -from pydantic import BaseModel, ConfigDict # from primaite.game.game import PrimaiteGame # TODO: Breaks things +from primaite.game.agent.actions.abstract import AbstractAction from primaite.interface.request import RequestFormat # TODO: Make sure that actions are backwards compatible where the old YAML format is used. -class AbstractAction(BaseModel): - """Base class for actions.""" - - # notes: - # we actually don't need to hold any state in actions, so there's no need to define any __init__ logic. - # all the init methods in the old actions are just used for holding a verb and shape, which are not really used. - # the config schema should be used to the actual parameters for formatting the action itself. - # (therefore there's no need for creating action instances, just the action class contains logic for converting - # CAOS actions to requests for simulator. Similar to the network node adder, that class also doesn't need to be - # instantiated.) - class ConfigSchema(BaseModel, ABC): # TODO: not sure if this better named something like `Options` - """Base configuration schema for Actions.""" - - model_config = ConfigDict(extra="forbid") - type: str - - _registry: ClassVar[Dict[str, Type[AbstractAction]]] = {} - - def __init_subclass__(cls, identifier: str, **kwargs: Any) -> None: - super().__init_subclass__(**kwargs) - if identifier in cls._registry: - raise ValueError(f"Cannot create new action under reserved name {identifier}") - cls._registry[identifier] = cls - - @classmethod - def form_request(self, config: ConfigSchema) -> RequestFormat: - """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - return [] - - class DoNothingAction(AbstractAction, identifier="do_nothing"): """Do Nothing Action.""" @@ -204,8 +173,8 @@ class ActionManager: # where `type` decides which AbstractAction subclass should be used # and `options` is an optional dict of options to pass to the init method of the action class act_type = act_spec.get("type") - act_options = act_spec.get("options", {}) - # self.actions[act_type] = self.act_class_identifiers[act_type](self, **global_action_args, **act_options) + # act_options = act_spec.get("options", {}) # Don't need this anymore I think? + self.actions[act_type] = AbstractAction._registry[act_type] self.action_map: Dict[int, Tuple[str, Dict]] = {} """ @@ -235,10 +204,10 @@ class ActionManager: :return: An action map maps consecutive integers to a combination of Action type and parameter choices. An example output could be: - {0: ("DONOTHING", {'dummy': 0}), - 1: ("NODE_OS_SCAN", {'node_id': 0}), - 2: ("NODE_OS_SCAN", {'node_id': 1}), - 3: ("NODE_FOLDER_SCAN", {'node_id:0, folder_id:0}), + {0: ("do_nothing", {'dummy': 0}), + 1: ("node_os_scan", {'node_name': computer}), + 2: ("node_os_scan", {'node_name': server}), + 3: ("node_folder_scan", {'node_name:computer, folder_name:downloads}), ... #etc... } :rtype: Dict[int, Tuple[AbstractAction, Dict]] @@ -269,7 +238,7 @@ class ActionManager: def form_request(self, action_identifier: str, action_options: Dict) -> RequestFormat: """Take action in CAOS format and use the execution definition to change it into PrimAITE request format.""" act_obj = self.actions[action_identifier] - return act_obj.form_request(**action_options) + return act_obj.form_request(action_options) @property def space(self) -> spaces.Space: diff --git a/src/primaite/notebooks/Action-masking.ipynb b/src/primaite/notebooks/Action-masking.ipynb index d22e171d..858b4bb6 100644 --- a/src/primaite/notebooks/Action-masking.ipynb +++ b/src/primaite/notebooks/Action-masking.ipynb @@ -19,8 +19,7 @@ "source": [ "from primaite.session.environment import PrimaiteGymEnv\n", "from primaite.config.load import data_manipulation_config_path\n", - "from prettytable import PrettyTable\n", - "UDP=\"UDP\"" + "from prettytable import PrettyTable" ] }, { From 6f6e4131b4cd39a108a93415cfdba20b1895b34a Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Tue, 29 Oct 2024 16:54:19 +0000 Subject: [PATCH 19/58] #2913: Handle case where server_ip_address is None --- src/primaite/simulator/system/applications/database_client.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/primaite/simulator/system/applications/database_client.py b/src/primaite/simulator/system/applications/database_client.py index cd4b2a03..e030b306 100644 --- a/src/primaite/simulator/system/applications/database_client.py +++ b/src/primaite/simulator/system/applications/database_client.py @@ -308,6 +308,8 @@ class DatabaseClient(Application, identifier="DatabaseClient"): """ if not self._can_perform_action(): return None + if self.server_ip_address is None: + return None connection_request_id = str(uuid4()) self._client_connection_requests[connection_request_id] = None From 3c1bb2d546a051eca445b88d7e700a3dea3f1861 Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Tue, 29 Oct 2024 16:57:11 +0000 Subject: [PATCH 20/58] #2913: Integration test fixes. --- src/primaite/simulator/system/core/session_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/primaite/simulator/system/core/session_manager.py b/src/primaite/simulator/system/core/session_manager.py index 75322e86..59390d68 100644 --- a/src/primaite/simulator/system/core/session_manager.py +++ b/src/primaite/simulator/system/core/session_manager.py @@ -16,7 +16,7 @@ from primaite.utils.validation.ip_protocol import IPProtocol, PROTOCOL_LOOKUP from primaite.utils.validation.port import Port, PORT_LOOKUP if TYPE_CHECKING: - from primaite.simulator.network.hardware.base import NetworkInterface + from primaite.simulator.network.hardware.base import NetworkInterface, Node from primaite.simulator.system.core.software_manager import SoftwareManager from primaite.simulator.system.core.sys_log import SysLog From 9fd862763b4dfac4f4015871d705cddd4d072eb5 Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Wed, 30 Oct 2024 11:11:07 +0000 Subject: [PATCH 21/58] #2913: Ensure optional software in config file is enabled. --- src/primaite/game/game.py | 2 +- src/primaite/simulator/system/applications/database_client.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/primaite/game/game.py b/src/primaite/game/game.py index c8fbac4e..7c2f49e7 100644 --- a/src/primaite/game/game.py +++ b/src/primaite/game/game.py @@ -376,7 +376,7 @@ class PrimaiteGame: if service_class is not None: _LOGGER.debug(f"installing {service_type} on node {new_node.hostname}") - new_node.software_manager.install(service_class) + new_node.software_manager.install(service_class, **service_cfg.get('options', {})) new_service = new_node.software_manager.software[service_class.__name__] # fixing duration for the service diff --git a/src/primaite/simulator/system/applications/database_client.py b/src/primaite/simulator/system/applications/database_client.py index e030b306..2b2be7b2 100644 --- a/src/primaite/simulator/system/applications/database_client.py +++ b/src/primaite/simulator/system/applications/database_client.py @@ -309,6 +309,9 @@ class DatabaseClient(Application, identifier="DatabaseClient"): if not self._can_perform_action(): return None if self.server_ip_address is None: + self.sys_log.warning( + f"{self.name}: Database server IP address not provided." + ) return None connection_request_id = str(uuid4()) From 97094aba795bfdeb5141f6d0080636635c74a377 Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Wed, 30 Oct 2024 11:15:39 +0000 Subject: [PATCH 22/58] #2913: Pre-commit changes. --- src/primaite/game/game.py | 2 +- src/primaite/simulator/system/applications/database_client.py | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/primaite/game/game.py b/src/primaite/game/game.py index 7c2f49e7..7c5c93bc 100644 --- a/src/primaite/game/game.py +++ b/src/primaite/game/game.py @@ -376,7 +376,7 @@ class PrimaiteGame: if service_class is not None: _LOGGER.debug(f"installing {service_type} on node {new_node.hostname}") - new_node.software_manager.install(service_class, **service_cfg.get('options', {})) + new_node.software_manager.install(service_class, **service_cfg.get("options", {})) new_service = new_node.software_manager.software[service_class.__name__] # fixing duration for the service diff --git a/src/primaite/simulator/system/applications/database_client.py b/src/primaite/simulator/system/applications/database_client.py index 2b2be7b2..2079194a 100644 --- a/src/primaite/simulator/system/applications/database_client.py +++ b/src/primaite/simulator/system/applications/database_client.py @@ -309,9 +309,7 @@ class DatabaseClient(Application, identifier="DatabaseClient"): if not self._can_perform_action(): return None if self.server_ip_address is None: - self.sys_log.warning( - f"{self.name}: Database server IP address not provided." - ) + self.sys_log.warning(f"{self.name}: Database server IP address not provided.") return None connection_request_id = str(uuid4()) From 77219db0411f50ddf422dbb9eba8cfe0c5aee0e5 Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Wed, 30 Oct 2024 16:32:49 +0000 Subject: [PATCH 23/58] #2913: Remove dns_server option from config files. --- tests/assets/configs/basic_switched_network.yaml | 2 -- tests/assets/configs/fix_duration_one_item.yaml | 6 ++---- tests/assets/configs/software_fix_duration.yaml | 4 +--- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/tests/assets/configs/basic_switched_network.yaml b/tests/assets/configs/basic_switched_network.yaml index fed0f52d..799bb571 100644 --- a/tests/assets/configs/basic_switched_network.yaml +++ b/tests/assets/configs/basic_switched_network.yaml @@ -202,8 +202,6 @@ simulation: port_scan_p_of_success: 0.8 services: - type: DNSClient - options: - dns_server: 192.168.1.10 - type: DNSServer options: domain_mapping: diff --git a/tests/assets/configs/fix_duration_one_item.yaml b/tests/assets/configs/fix_duration_one_item.yaml index bd0fb61f..4163bcfd 100644 --- a/tests/assets/configs/fix_duration_one_item.yaml +++ b/tests/assets/configs/fix_duration_one_item.yaml @@ -200,8 +200,6 @@ simulation: port_scan_p_of_success: 0.8 services: - type: DNSClient - options: - dns_server: 192.168.1.10 - type: DNSServer options: domain_mapping: @@ -232,8 +230,8 @@ simulation: server_password: arcd services: - type: DNSClient - options: - dns_server: 192.168.1.10 + # options: + # dns_server: 192.168.1.10 links: - endpoint_a_hostname: switch_1 diff --git a/tests/assets/configs/software_fix_duration.yaml b/tests/assets/configs/software_fix_duration.yaml index 1a28258b..2b72e85f 100644 --- a/tests/assets/configs/software_fix_duration.yaml +++ b/tests/assets/configs/software_fix_duration.yaml @@ -209,7 +209,7 @@ simulation: services: - type: DNSClient options: - dns_server: 192.168.1.10 + # dns_server: 192.168.1.10 fix_duration: 3 - type: DNSServer options: @@ -250,8 +250,6 @@ simulation: server_password: arcd services: - type: DNSClient - options: - dns_server: 192.168.1.10 links: - endpoint_a_hostname: switch_1 From 7d977c809538ce236d38b21aec1c97787abfb29a Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Wed, 30 Oct 2024 16:33:14 +0000 Subject: [PATCH 24/58] #2913: Fix config path for test. --- .../integration_tests/extensions/test_extendable_config.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/integration_tests/extensions/test_extendable_config.py b/tests/integration_tests/extensions/test_extendable_config.py index 5addcbd7..71a60194 100644 --- a/tests/integration_tests/extensions/test_extendable_config.py +++ b/tests/integration_tests/extensions/test_extendable_config.py @@ -5,6 +5,7 @@ from primaite.config.load import get_extended_config_path from primaite.simulator.network.container import Network from primaite.simulator.network.hardware.node_operating_state import NodeOperatingState from primaite.simulator.network.hardware.nodes.host.computer import Computer +from tests import TEST_ASSETS_ROOT from tests.integration_tests.configuration_file_parsing import BASIC_CONFIG, DMZ_NETWORK, load_config from tests.integration_tests.extensions.applications.extended_application import ExtendedApplication from tests.integration_tests.extensions.nodes.giga_switch import GigaSwitch @@ -13,11 +14,12 @@ from tests.integration_tests.extensions.nodes.giga_switch import GigaSwitch from tests.integration_tests.extensions.nodes.super_computer import SuperComputer from tests.integration_tests.extensions.services.extended_service import ExtendedService +CONFIG_PATH = TEST_ASSETS_ROOT / "configs/extended_config.yaml" + def test_extended_example_config(): """Test that the example config can be parsed properly.""" - config_path = os.path.join("tests", "assets", "configs", "extended_config.yaml") - game = load_config(config_path) + game = load_config(CONFIG_PATH) network: Network = game.simulation.network assert len(network.nodes) == 10 # 10 nodes in example network From 844a3a60fa4f26d95baa0585d8112a566fde0208 Mon Sep 17 00:00:00 2001 From: Charlie Crane Date: Wed, 30 Oct 2024 18:34:05 +0000 Subject: [PATCH 25/58] #2912 - Steps to get test_actions passing the refactored actions. Some linting changes and YAML updates. --- src/primaite/game/agent/actions/abstract.py | 22 +-- src/primaite/game/agent/actions/acl.py | 145 ++++++++++-------- .../game/agent/actions/application.py | 51 +++++- src/primaite/game/agent/actions/config.py | 22 +-- src/primaite/game/agent/actions/file.py | 86 ++++++++++- src/primaite/game/agent/actions/folder.py | 37 ++++- src/primaite/game/agent/actions/host_nic.py | 18 ++- src/primaite/game/agent/actions/manager.py | 120 +-------------- src/primaite/game/agent/actions/network.py | 13 +- src/primaite/game/agent/actions/node.py | 3 +- src/primaite/game/agent/actions/service.py | 20 ++- src/primaite/game/agent/actions/session.py | 35 ++++- .../configs/firewall_actions_network.yaml | 4 +- ...etwork_service_recon_red_agent_config.yaml | 5 +- .../nmap_ping_scan_red_agent_config.yaml | 7 +- .../nmap_port_scan_red_agent_config.yaml | 6 +- tests/conftest.py | 96 ++++++------ .../game_layer/test_actions.py | 86 +++++------ .../_game/_agent/test_probabilistic_agent.py | 24 +-- 19 files changed, 480 insertions(+), 320 deletions(-) diff --git a/src/primaite/game/agent/actions/abstract.py b/src/primaite/game/agent/actions/abstract.py index 5250e532..b96b14c9 100644 --- a/src/primaite/game/agent/actions/abstract.py +++ b/src/primaite/game/agent/actions/abstract.py @@ -1,5 +1,6 @@ # © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from __future__ import annotations + from abc import ABC from typing import Any, ClassVar, Dict, Type @@ -7,6 +8,7 @@ from pydantic import BaseModel, ConfigDict from primaite.interface.request import RequestFormat + class AbstractAction(BaseModel): """Base class for actions.""" @@ -34,15 +36,17 @@ class AbstractAction(BaseModel): @classmethod def form_request(cls, config: ConfigSchema) -> RequestFormat: """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - return [] - + pass + @classmethod def from_config(cls, config: Dict) -> "AbstractAction": - """Create an action component from a config dictionary""" - - type_id = config.get("type") + """Create an action component from a config dictionary.""" + # set attributes for action based off config dict + # if config["type"] not in cls._registry: + # raise ValueError(f"Invalid action reward type {config['type']}") - if type_id in cls._registry: - return cls(type=type_id, model_config=config) - else: - return [] \ No newline at end of file + for attribute, value in config.items(): + if not hasattr(cls.ConfigSchema, attribute): + setattr(cls.ConfigSchema, attribute, value) + + return cls diff --git a/src/primaite/game/agent/actions/acl.py b/src/primaite/game/agent/actions/acl.py index cc89bfba..8f5a79da 100644 --- a/src/primaite/game/agent/actions/acl.py +++ b/src/primaite/game/agent/actions/acl.py @@ -14,6 +14,16 @@ __all__ = ( ) +class ACLAbstractAction(AbstractAction, identifier="acl_abstract_action"): + """Base class for ACL actions.""" + + + class ConfigSchema(AbstractAction.ConfigSchema): + """Configuration Schema base for ACL abstract actions.""" + + + + class RouterACLAddRuleAction(AbstractAction, identifier="router_acl_add_rule"): """Action which adds a rule to a router's ACL.""" @@ -26,9 +36,9 @@ class RouterACLAddRuleAction(AbstractAction, identifier="router_acl_add_rule"): source_ip_id: int source_wildcard_id: int source_port_id: int - dest_ip_id: int - dest_wildcard_id: int - dest_port_id: int + dst_ip: str + dst_wildcard_id: int + dst_port: int protocol_name: str class ACLRuleOptions(BaseModel): @@ -46,13 +56,13 @@ class RouterACLAddRuleAction(AbstractAction, identifier="router_acl_add_rule"): """Rule source IP wildcard. By default, use the wildcard at index 0 from action manager.""" source_port_id: int = Field(default=1, ge=1) """Rule source port. By default, all source ports.""" - dest_ip_id: int = Field(default=1, ge=1) + dst_ip_id: int = Field(default=1, ge=1) """Rule destination IP address. By default, all ip addresses.""" - dest_wildcard_id: int = Field(default=0, ge=0) + dst_wildcard_id: int = Field(default=0, ge=0) """Rule destination IP wildcard. By default, use the wildcard at index 0 from action manager.""" - dest_port_id: int = Field(default=1, ge=1) + dst_port_id: int = Field(default=1, ge=1) """Rule destination port. By default, all destination ports.""" - protocol_id: int = Field(default=1, ge=1) + protocol_name: str = "ALL" """Rule protocol. By default, all protocols.""" @field_validator( @@ -62,7 +72,7 @@ class RouterACLAddRuleAction(AbstractAction, identifier="router_acl_add_rule"): "dest_ip_id", "dest_port_id", "dest_wildcard_id", - "protocol_id", + "protocol_name", mode="before", ) @classmethod @@ -82,10 +92,10 @@ class RouterACLAddRuleAction(AbstractAction, identifier="router_acl_add_rule"): permission=config.permission, source_ip_id=config.source_ip_id, source_wildcard_id=config.source_wildcard_id, - dest_ip_id=config.dest_ip_id, + dest_ip_id=config.dst_ip, dest_wildcard_id=config.dest_wildcard_id, source_port_id=config.source_port_id, - dest_port_id=config.dest_port_id, + dest_port_id=config.dst_port_id, protocol=config.protocol_name, ) if parsed_options.permission == 1: @@ -95,10 +105,10 @@ class RouterACLAddRuleAction(AbstractAction, identifier="router_acl_add_rule"): # else: # _LOGGER.warning(f"{self.__class__} received permission {permission}, expected 0 or 1.") - if parsed_options.protocol_id == 1: + if parsed_options.protocol_name == "ALL": protocol = "ALL" else: - protocol = cls.manager.get_internet_protocol_by_idx(parsed_options.protocol_id - 2) + protocol = parsed_options.protocol_name # subtract 2 to account for UNUSED=0 and ALL=1. if parsed_options.source_ip_id == 1: @@ -120,7 +130,9 @@ class RouterACLAddRuleAction(AbstractAction, identifier="router_acl_add_rule"): else: dst_ip = cls.manager.get_ip_address_by_idx(parsed_options.dest_ip_id - 2) # subtract 2 to account for UNUSED=0, and ALL=1 - dst_wildcard = cls.manager.get_wildcard_by_idx(parsed_options.dest_wildcard_id) + dst_ip=config.dst_ip + + dst_wildcard = config.dest_wildcard_id if parsed_options.dest_port_id == 1: dst_port = "ALL" @@ -134,14 +146,14 @@ class RouterACLAddRuleAction(AbstractAction, identifier="router_acl_add_rule"): config.target_router, "acl", "add_rule", - permission_str, + config.permission_str, protocol, str(src_ip), - src_wildcard, - src_port, - str(dst_ip), - dst_wildcard, - dst_port, + config.src_wildcard, + config.src_port, + str(config.dst_ip), + config.dst_wildcard, + config.dst_port, config.position, ] @@ -161,9 +173,27 @@ class RouterACLRemoveRuleAction(AbstractAction, identifier="router_acl_remove_ru return ["network", "node", config.target_router, "acl", "remove_rule", config.position] -class FirewallACLAddRuleAction(AbstractAction, identifier="firewall_acl_add_rule"): +class FirewallACLAddRuleAction(ACLAbstractAction, identifier="firewall_acl_add_rule"): """Action which adds a rule to a firewall port's ACL.""" + max_acl_rules: int + num_ips: int + num_ports: int + num_protocols: int + num_permissions: int = 3 + permission: str + + class ConfigSchema(ACLAbstractAction.ConfigSchema): + """Configuration schema for FirewallACLAddRuleAction.""" + + max_acl_rules: int + num_ips: int + num_ports: int + num_protocols: int + num_permissions: int = 3 + permission: str + + def __init__( self, manager: "ActionManager", @@ -198,92 +228,85 @@ class FirewallACLAddRuleAction(AbstractAction, identifier="firewall_acl_add_rule "protocol_id": num_protocols, } - def form_request( - self, - target_firewall_nodename: str, - firewall_port_name: str, - firewall_port_direction: str, - position: int, - permission: int, - source_ip_id: int, - source_wildcard_id: int, - dest_ip_id: int, - dest_wildcard_id: int, - source_port_id: int, - dest_port_id: int, - protocol_id: int, - ) -> List[str]: + + + @classmethod + def form_request(cls, config:ConfigSchema) -> List[str]: """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - if permission == 0: + if config.permission == 0: permission_str = "UNUSED" return ["do_nothing"] # NOT SUPPORTED, JUST DO NOTHING IF WE COME ACROSS THIS - elif permission == 1: + elif config.permission == 1: permission_str = "PERMIT" - elif permission == 2: + elif config.permission == 2: permission_str = "DENY" # else: # _LOGGER.warning(f"{self.__class__} received permission {permission}, expected 0 or 1.") - if protocol_id == 0: + if config.protocol_id == 0: return ["do_nothing"] # NOT SUPPORTED, JUST DO NOTHING IF WE COME ACROSS THIS - if protocol_id == 1: + if config.protocol_id == 1: protocol = "ALL" else: - protocol = self.manager.get_internet_protocol_by_idx(protocol_id - 2) + # protocol = self.manager.get_internet_protocol_by_idx(protocol_id - 2) # subtract 2 to account for UNUSED=0 and ALL=1. + pass - if source_ip_id == 0: + if config.source_ip_id == 0: return ["do_nothing"] # invalid formulation - elif source_ip_id == 1: + elif config.source_ip_id == 1: src_ip = "ALL" else: - src_ip = self.manager.get_ip_address_by_idx(source_ip_id - 2) + # src_ip = self.manager.get_ip_address_by_idx(source_ip_id - 2) # subtract 2 to account for UNUSED=0, and ALL=1 - if source_port_id == 0: + if config.source_port_id == 0: return ["do_nothing"] # invalid formulation - elif source_port_id == 1: + elif config.source_port_id == 1: src_port = "ALL" else: - src_port = self.manager.get_port_by_idx(source_port_id - 2) + # src_port = self.manager.get_port_by_idx(source_port_id - 2) # subtract 2 to account for UNUSED=0, and ALL=1 + pass - if dest_ip_id == 0: + if config.dest_ip_id == 0: return ["do_nothing"] # invalid formulation - elif dest_ip_id == 1: + elif config.dest_ip_id == 1: dst_ip = "ALL" else: - dst_ip = self.manager.get_ip_address_by_idx(dest_ip_id - 2) + # dst_ip = self.manager.get_ip_address_by_idx(dest_ip_id - 2) # subtract 2 to account for UNUSED=0, and ALL=1 + pass - if dest_port_id == 0: + if config.dest_port_id == 0: return ["do_nothing"] # invalid formulation - elif dest_port_id == 1: + elif config.dest_port_id == 1: dst_port = "ALL" else: - dst_port = self.manager.get_port_by_idx(dest_port_id - 2) + # dst_port = self.manager.get_port_by_idx(dest_port_id - 2) # subtract 2 to account for UNUSED=0, and ALL=1 - src_wildcard = self.manager.get_wildcard_by_idx(source_wildcard_id) - dst_wildcard = self.manager.get_wildcard_by_idx(dest_wildcard_id) + # src_wildcard = self.manager.get_wildcard_by_idx(source_wildcard_id) + # dst_wildcard = self.manager.get_wildcard_by_idx(dest_wildcard_id) + pass return [ "network", "node", - target_firewall_nodename, - firewall_port_name, - firewall_port_direction, + config.target_firewall_nodename, + config.firewall_port_name, + config.firewall_port_direction, "acl", "add_rule", permission_str, protocol, str(src_ip), - src_wildcard, + config.src_wildcard, src_port, str(dst_ip), - dst_wildcard, + config.dst_wildcard, dst_port, - position, + config.position, ] diff --git a/src/primaite/game/agent/actions/application.py b/src/primaite/game/agent/actions/application.py index e0496dc7..942ebe90 100644 --- a/src/primaite/game/agent/actions/application.py +++ b/src/primaite/game/agent/actions/application.py @@ -35,12 +35,21 @@ class NodeApplicationAbstractAction(AbstractAction, identifier="node_application """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" if config.node_name is None or config.application_name is None: return ["do_nothing"] - return ["network", "node", config.node_name, "application", config.application_name, cls.verb] + return [ + "network", + "node", + config.node_name, + "application", + config.application_name, + cls.model_fields["verb"].default, + ] class NodeApplicationExecuteAction(NodeApplicationAbstractAction, identifier="node_application_execute"): """Action which executes an application.""" + verb: str = "execute" + class ConfigSchema(NodeApplicationAbstractAction.ConfigSchema): """Configuration schema for NodeApplicationExecuteAction.""" @@ -50,6 +59,8 @@ class NodeApplicationExecuteAction(NodeApplicationAbstractAction, identifier="no class NodeApplicationScanAction(NodeApplicationAbstractAction, identifier="node_application_scan"): """Action which scans an application.""" + verb: str = "scan" + class ConfigSchema(NodeApplicationAbstractAction.ConfigSchema): """Configuration schema for NodeApplicationScanAction.""" @@ -59,6 +70,8 @@ class NodeApplicationScanAction(NodeApplicationAbstractAction, identifier="node_ class NodeApplicationCloseAction(NodeApplicationAbstractAction, identifier="node_application_close"): """Action which closes an application.""" + verb: str = "close" + class ConfigSchema(NodeApplicationAbstractAction.ConfigSchema): """Configuration schema for NodeApplicationCloseAction.""" @@ -68,6 +81,8 @@ class NodeApplicationCloseAction(NodeApplicationAbstractAction, identifier="node class NodeApplicationFixAction(NodeApplicationAbstractAction, identifier="node_application_fix"): """Action which fixes an application.""" + verb: str = "fix" + class ConfigSchema(NodeApplicationAbstractAction.ConfigSchema): """Configuration schema for NodeApplicationFixAction.""" @@ -77,18 +92,50 @@ class NodeApplicationFixAction(NodeApplicationAbstractAction, identifier="node_a class NodeApplicationInstallAction(NodeApplicationAbstractAction, identifier="node_application_install"): """Action which installs an application.""" + verb: str = "install" + class ConfigSchema(NodeApplicationAbstractAction.ConfigSchema): """Configuration schema for NodeApplicationInstallAction.""" verb: str = "install" - # TODO: Either changes to application form_request bits, or add that here. + @classmethod + def form_request(cls, config: ConfigSchema) -> RequestFormat: + """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" + if config.node_name is None: + return ["do_nothing"] + return [ + "network", + "node", + config.node_name, + "software_manager", + "application", + cls.model_fields["verb"].default, + config.application_name, + ] class NodeApplicationRemoveAction(NodeApplicationAbstractAction, identifier="node_application_remove"): """Action which removes/uninstalls an application.""" + verb: str = "uninstall" + class ConfigSchema(NodeApplicationAbstractAction.ConfigSchema): """Configuration schema for NodeApplicationRemoveAction.""" verb: str = "uninstall" + + @classmethod + def form_request(cls, config: ConfigSchema) -> RequestFormat: + """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" + if config.node_name is None: + return ["do_nothing"] + return [ + "network", + "node", + config.node_name, + "software_manager", + "application", + cls.model_fields["verb"].default, + config.application_name, + ] diff --git a/src/primaite/game/agent/actions/config.py b/src/primaite/game/agent/actions/config.py index 582e8ec7..beda8f27 100644 --- a/src/primaite/game/agent/actions/config.py +++ b/src/primaite/game/agent/actions/config.py @@ -18,7 +18,7 @@ __all__ = ( ) -class ConfigureRansomwareScriptAction(AbstractAction, identifier="configure_ransomware"): +class ConfigureRansomwareScriptAction(AbstractAction, identifier="c2_server_ransomware_configure"): """Action which sets config parameters for a ransomware script on a node.""" class ConfigSchema(AbstractAction.ConfigSchema): @@ -66,7 +66,7 @@ class ConfigureDoSBotAction(AbstractAction, identifier="configure_dos_bot"): return ["network", "node", node_name, "application", "DoSBot", "configure", config] -class ConfigureC2BeaconAction(AbstractAction, identifier="configure_c2"): +class ConfigureC2BeaconAction(AbstractAction, identifier="configure_c2_beacon"): """Action which configures a C2 Beacon based on the parameters given.""" class ConfigSchema(AbstractAction.ConfigSchema): @@ -105,14 +105,14 @@ class ConfigureC2BeaconAction(AbstractAction, identifier="configure_c2"): """Return the action formatted as a request that can be ingested by the simulation.""" if config.node_name is None: return ["do_nothing"] - config = ConfigureC2BeaconAction._Opts( - c2_server_ip_address=config["c2_server_ip_address"], - keep_alive_frequency=config["keep_alive_frequency"], - masquerade_port=config["masquerade_port"], - masquerade_protocol=config["masquerade_protocol"], + configuration = ConfigureC2BeaconAction._Opts( + c2_server_ip_address=config.c2_server_ip_address, + keep_alive_frequency=config.keep_alive_frequency, + masquerade_port=config.masquerade_port, + masquerade_protocol=config.masquerade_protocol, ) - ConfigureC2BeaconAction._Opts.model_validate(config) # check that options adhere to schema + ConfigureC2BeaconAction._Opts.model_validate(configuration) # check that options adhere to schema return ["network", "node", config.node_name, "application", "C2Beacon", "configure", config.__dict__] @@ -142,7 +142,7 @@ class NodeSendRemoteCommandAction(AbstractAction, identifier="node_send_remote_c ] -class TerminalC2ServerAction(AbstractAction, identifier="terminal_c2_server"): +class TerminalC2ServerAction(AbstractAction, identifier="c2_server_terminal_command"): """Action which causes the C2 Server to send a command to the C2 Beacon to execute the terminal command passed.""" class _Opts(BaseModel): @@ -173,7 +173,7 @@ class TerminalC2ServerAction(AbstractAction, identifier="terminal_c2_server"): return ["network", "node", node_name, "application", "C2Server", "terminal_command", command_model] -class RansomwareLaunchC2ServerAction(AbstractAction, identifier="ransomware_launch"): +class RansomwareLaunchC2ServerAction(AbstractAction, identifier="c2_server_ransomware_launch"): """Action which causes the C2 Server to send a command to the C2 Beacon to launch the RansomwareScript.""" class ConfigSchema(AbstractAction.ConfigSchema): @@ -190,7 +190,7 @@ class RansomwareLaunchC2ServerAction(AbstractAction, identifier="ransomware_laun return ["network", "node", config.node_name, "application", "C2Server", "ransomware_launch"] -class ExfiltrationC2ServerAction(AbstractAction, identifier="exfiltration_c2_server"): +class ExfiltrationC2ServerAction(AbstractAction, identifier="c2_server_data_exfiltrate"): """Action which exfiltrates a target file from a certain node onto the C2 beacon and then the C2 Server.""" class _Opts(BaseModel): diff --git a/src/primaite/game/agent/actions/file.py b/src/primaite/game/agent/actions/file.py index 6935a11c..6d1f95b9 100644 --- a/src/primaite/game/agent/actions/file.py +++ b/src/primaite/game/agent/actions/file.py @@ -44,22 +44,45 @@ class NodeFileAbstractAction(AbstractAction, identifier="node_file_abstract_acti config.folder_name, "file", config.file_name, - cls.verb, + cls.model_fields["verb"].default, ] class NodeFileCreateAction(NodeFileAbstractAction, identifier="node_file_create"): """Action which creates a new file in a given folder.""" + verb: str = "create" + force: bool = False + class ConfigSchema(NodeFileAbstractAction.ConfigSchema): """Configuration schema for NodeFileCreateAction.""" verb: str = "create" + force: bool = False + + @classmethod + def form_request(cls, config: ConfigSchema) -> RequestFormat: + """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" + if config.node_name is None or config.folder_name is None or config.file_name is None: + return ["do_nothing"] + return [ + "network", + "node", + config.node_name, + "file_system", + cls.model_fields["verb"].default, + "file", + config.folder_name, + config.file_name, + cls.model_fields["force"].default, + ] class NodeFileScanAction(NodeFileAbstractAction, identifier="node_file_scan"): """Action which scans a file.""" + verb: str = "scan" + class ConfigSchema(NodeFileAbstractAction.ConfigSchema): """Configuration schema for NodeFileScanAction.""" @@ -69,15 +92,35 @@ class NodeFileScanAction(NodeFileAbstractAction, identifier="node_file_scan"): class NodeFileDeleteAction(NodeFileAbstractAction, identifier="node_file_delete"): """Action which deletes a file.""" + verb: str = "delete" + class ConfigSchema(NodeFileAbstractAction.ConfigSchema): """Configuration schema for NodeFileDeleteAction.""" verb: str = "delete" + @classmethod + def form_request(cls, config: ConfigSchema) -> RequestFormat: + """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" + if config.node_name is None or config.folder_name is None or config.file_name is None: + return ["do_nothing"] + return [ + "network", + "node", + config.node_name, + "file_system", + cls.model_fields["verb"].default, + "file", + config.folder_name, + config.file_name, + ] + class NodeFileRestoreAction(NodeFileAbstractAction, identifier="node_file_restore"): """Action which restores a file.""" + verb: str = "restore" + class ConfigSchema(NodeFileAbstractAction.ConfigSchema): """Configuration schema for NodeFileRestoreAction.""" @@ -87,6 +130,8 @@ class NodeFileRestoreAction(NodeFileAbstractAction, identifier="node_file_restor class NodeFileCorruptAction(NodeFileAbstractAction, identifier="node_file_corrupt"): """Action which corrupts a file.""" + verb: str = "corrupt" + class ConfigSchema(NodeFileAbstractAction.ConfigSchema): """Configuration schema for NodeFileCorruptAction.""" @@ -96,7 +141,46 @@ class NodeFileCorruptAction(NodeFileAbstractAction, identifier="node_file_corrup class NodeFileAccessAction(NodeFileAbstractAction, identifier="node_file_access"): """Action which increases a file's access count.""" + verb: str = "access" + class ConfigSchema(NodeFileAbstractAction.ConfigSchema): """Configuration schema for NodeFileAccessAction.""" verb: str = "access" + + @classmethod + def form_request(cls, config: ConfigSchema) -> RequestFormat: + """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" + if config.node_name is None or config.folder_name is None or config.file_name is None: + return ["do_nothing"] + return [ + "network", + "node", + config.node_name, + "file_system", + cls.model_fields["verb"].default, + config.folder_name, + config.file_name, + ] + + +class NodeFileCheckhashAction(NodeFileAbstractAction, identifier="node_file_checkhash"): + """Action which checks the hash of a file.""" + + verb: str = "checkhash" + + class ConfigSchema(NodeFileAbstractAction.ConfigSchema): + """Configuration schema for NodeFileCheckhashAction.""" + + verb: str = "checkhash" + + +class NodeFileRepairAction(NodeFileAbstractAction, identifier="node_file_repair"): + """Action which repairs a file""" + + verb: str = "repair" + + class ConfigSchema(NodeFileAbstractAction.ConfigSchema): + """Configuration Schema for NodeFileRepairAction.""" + + verb: str = "repair" diff --git a/src/primaite/game/agent/actions/folder.py b/src/primaite/game/agent/actions/folder.py index 74820eb0..e430efb7 100644 --- a/src/primaite/game/agent/actions/folder.py +++ b/src/primaite/game/agent/actions/folder.py @@ -34,12 +34,22 @@ class NodeFolderAbstractAction(AbstractAction, identifier="node_folder_abstract" """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" if config.node_name is None or config.folder_name is None: return ["do_nothing"] - return ["network", "node", config.node_name, "file_system", "folder", config.folder_name, cls.verb] + return [ + "network", + "node", + config.node_name, + "file_system", + "folder", + config.folder_name, + cls.model_fields["verb"].default, + ] class NodeFolderScanAction(NodeFolderAbstractAction, identifier="node_folder_scan"): """Action which scans a folder.""" + verb: str = "scan" + class ConfigSchema(NodeFolderAbstractAction.ConfigSchema): """Configuration schema for NodeFolderScanAction.""" @@ -49,6 +59,8 @@ class NodeFolderScanAction(NodeFolderAbstractAction, identifier="node_folder_sca class NodeFolderCheckhashAction(NodeFolderAbstractAction, identifier="node_folder_checkhash"): """Action which checks the hash of a folder.""" + verb: str = "checkhash" + class ConfigSchema(NodeFolderAbstractAction.ConfigSchema): """Configuration schema for NodeFolderCheckhashAction.""" @@ -58,6 +70,8 @@ class NodeFolderCheckhashAction(NodeFolderAbstractAction, identifier="node_folde class NodeFolderRepairAction(NodeFolderAbstractAction, identifier="node_folder_repair"): """Action which repairs a folder.""" + verb: str = "repair" + class ConfigSchema(NodeFolderAbstractAction.ConfigSchema): """Configuration schema for NodeFolderRepairAction.""" @@ -67,16 +81,35 @@ class NodeFolderRepairAction(NodeFolderAbstractAction, identifier="node_folder_r class NodeFolderRestoreAction(NodeFolderAbstractAction, identifier="node_folder_restore"): """Action which restores a folder.""" + verb: str = "restore" + class ConfigSchema(NodeFolderAbstractAction.ConfigSchema): """Configuration schema for NodeFolderRestoreAction.""" verb: str = "restore" -class NodeFolderCreateAction(AbstractAction, identifier="node_folder_create"): +class NodeFolderCreateAction(NodeFolderAbstractAction, identifier="node_folder_create"): """Action which creates a new folder.""" + verb: str = "create" + class ConfigSchema(NodeFolderAbstractAction.ConfigSchema): """Configuration schema for NodeFolderCreateAction.""" verb: str = "create" + + @classmethod + def form_request(cls, config: ConfigSchema) -> RequestFormat: + """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" + if config.node_name is None or config.folder_name is None: + return ["do_nothing"] + return [ + "network", + "node", + config.node_name, + "file_system", + cls.model_fields["verb"].default, + "folder", + config.folder_name, + ] diff --git a/src/primaite/game/agent/actions/host_nic.py b/src/primaite/game/agent/actions/host_nic.py index 4f66f9b9..1ad2e52f 100644 --- a/src/primaite/game/agent/actions/host_nic.py +++ b/src/primaite/game/agent/actions/host_nic.py @@ -13,11 +13,12 @@ class HostNICAbstractAction(AbstractAction, identifier="host_nic_abstract"): class. """ + node_name: str + nic_num: str + class ConfigSchema(AbstractAction.ConfigSchema): """Base Configuration schema for HostNIC actions.""" - num_nodes: str - max_nics_per_node: str node_name: str nic_num: str @@ -26,12 +27,21 @@ class HostNICAbstractAction(AbstractAction, identifier="host_nic_abstract"): """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" if config.node_name is None or config.nic_num is None: return ["do_nothing"] - return ["network", "node", config.node_name, "network_interface", config.nic_num, cls.verb] + return [ + "network", + "node", + config.node_name, + "network_interface", + config.nic_num, + cls.model_fields["verb"].default, + ] class HostNICEnableAction(HostNICAbstractAction, identifier="host_nic_enable"): """Action which enables a NIC.""" + verb: str = "enable" + class ConfigSchema(HostNICAbstractAction.ConfigSchema): """Configuration schema for HostNICEnableAction.""" @@ -41,6 +51,8 @@ class HostNICEnableAction(HostNICAbstractAction, identifier="host_nic_enable"): class HostNICDisableAction(HostNICAbstractAction, identifier="host_nic_disable"): """Action which disables a NIC.""" + verb: str = "disable" + class ConfigSchema(HostNICAbstractAction.ConfigSchema): """Configuration schema for HostNICDisableAction.""" diff --git a/src/primaite/game/agent/actions/manager.py b/src/primaite/game/agent/actions/manager.py index 98ffbd00..26814bf2 100644 --- a/src/primaite/game/agent/actions/manager.py +++ b/src/primaite/game/agent/actions/manager.py @@ -33,7 +33,8 @@ class DoNothingAction(AbstractAction, identifier="do_nothing"): type: Literal["do_nothing"] = "do_nothing" - def form_request(self, options: ConfigSchema) -> RequestFormat: + @classmethod + def form_request(cls, config: ConfigSchema) -> RequestFormat: """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" return ["do_nothing"] @@ -44,17 +45,6 @@ class ActionManager: def __init__( self, actions: List[Dict], # stores list of actions available to agent - # nodes: List[Dict], # extra configuration for each node - # max_folders_per_node: int = 2, # allows calculating shape - # max_files_per_folder: int = 2, # allows calculating shape - # max_services_per_node: int = 2, # allows calculating shape - # max_applications_per_node: int = 2, # allows calculating shape - # max_nics_per_node: int = 8, # allows calculating shape - # max_acl_rules: int = 10, # allows calculating shape - # protocols: List[str] = ["TCP", "UDP", "ICMP"], # allow mapping index to protocol - # ports: List[str] = ["HTTP", "DNS", "ARP", "FTP", "NTP"], # allow mapping index to port - # ip_list: List[str] = [], # to allow us to map an index to an ip address. - # wildcard_list: List[str] = [], # to allow mapping from wildcard index to act_map: Optional[Dict[int, Dict]] = None, # allows restricting set of possible actions *args, **kwargs, @@ -66,114 +56,12 @@ class ActionManager: :param actions: List of action specs which should be made available to the agent. The keys of each spec are: 'type' and 'options' for passing any options to the action class's init method :type actions: List[dict] - :param nodes: Extra configuration for each node. - :type nodes: List[Dict] - :param max_folders_per_node: Maximum number of folders per node. Used for calculating action shape. - :type max_folders_per_node: int - :param max_files_per_folder: Maximum number of files per folder. Used for calculating action shape. - :type max_files_per_folder: int - :param max_services_per_node: Maximum number of services per node. Used for calculating action shape. - :type max_services_per_node: int - :param max_nics_per_node: Maximum number of NICs per node. Used for calculating action shape. - :type max_nics_per_node: int - :param max_acl_rules: Maximum number of ACL rules per router. Used for calculating action shape. - :type max_acl_rules: int - :param protocols: List of protocols that are available in the simulation. Used for calculating action shape. - :type protocols: List[str] - :param ports: List of ports that are available in the simulation. Used for calculating action shape. - :type ports: List[str] - :param ip_list: List of IP addresses that known to this agent. Used for calculating action shape. - :type ip_list: Optional[List[str]] :param act_map: Action map which maps integers to actions. Used for restricting the set of possible actions. :type act_map: Optional[Dict[int, Dict]] """ - # self.node_names: List[str] = [n["node_name"] for n in nodes] - """List of node names in this action space. The list order is the mapping between node index and node name.""" - # self.application_names: List[List[str]] = [] - """ - List of applications per node. The list order gives the two-index mapping between (node_id, app_id) to app name. - The first index corresponds to node id, the second index is the app id on that particular node. - For instance, self.application_names[0][2] is the name of the third application on the first node. - """ - # self.service_names: List[List[str]] = [] - """ - List of services per node. The list order gives the two-index mapping between (node_id, svc_id) to svc name. - The first index corresponds to node id, the second index is the service id on that particular node. - For instance, self.service_names[0][2] is the name of the third service on the first node. - """ - # self.folder_names: List[List[str]] = [] - """ - List of folders per node. The list order gives the two-index mapping between (node_id, folder_id) to folder - name. The first index corresponds to node id, the second index is the folder id on that particular node. - For instance, self.folder_names[0][2] is the name of the third folder on the first node. - """ - # self.file_names: List[List[List[str]]] = [] - """ - List of files per folder per node. The list order gives the three-index mapping between - (node_id, folder_id, file_id) to file name. The first index corresponds to node id, the second index is the - folder id on that particular node, and the third index is the file id in that particular folder. - For instance, self.file_names[0][2][1] is the name of the second file in the third folder on the first node. - """ - - # Populate lists of apps, services, files, folders, etc on nodes. - # for node in nodes: - # app_list = [a["application_name"] for a in node.get("applications", [])] - # while len(app_list) < max_applications_per_node: - # app_list.append(None) - # self.application_names.append(app_list) - - # svc_list = [s["service_name"] for s in node.get("services", [])] - # while len(svc_list) < max_services_per_node: - # svc_list.append(None) - # self.service_names.append(svc_list) - - # folder_list = [f["folder_name"] for f in node.get("folders", [])] - # while len(folder_list) < max_folders_per_node: - # folder_list.append(None) - # self.folder_names.append(folder_list) - - # file_sublist = [] - # for folder in node.get("folders", [{"files": []}]): - # file_list = [f["file_name"] for f in folder.get("files", [])] - # while len(file_list) < max_files_per_folder: - # file_list.append(None) - # file_sublist.append(file_list) - # while len(file_sublist) < max_folders_per_node: - # file_sublist.append([None] * max_files_per_folder) - # self.file_names.append(file_sublist) - # self.protocols: List[str] = protocols - # self.ports: List[str] = ports - - # self.ip_address_list: List[str] = ip_list - # self.wildcard_list: List[str] = wildcard_list - # if self.wildcard_list == []: - # self.wildcard_list = ["NONE"] - # # action_args are settings which are applied to the action space as a whole. - # global_action_args = { - # "num_nodes": len(self.node_names), - # "num_folders": max_folders_per_node, - # "num_files": max_files_per_folder, - # "num_services": max_services_per_node, - # "num_applications": max_applications_per_node, - # "num_nics": max_nics_per_node, - # "num_acl_rules": max_acl_rules, - # "num_protocols": len(self.protocols), - # "num_ports": len(self.protocols), - # "num_ips": len(self.ip_address_list), - # "max_acl_rules": max_acl_rules, - # "max_nics_per_node": max_nics_per_node, - # } self.actions: Dict[str, AbstractAction] = {} for act_spec in actions: - # each action is provided into the action space config like this: - # - type: ACTION_TYPE - # options: - # option_1: value1 - # option_2: value2 - # where `type` decides which AbstractAction subclass should be used - # and `options` is an optional dict of options to pass to the init method of the action class act_type = act_spec.get("type") - # act_options = act_spec.get("options", {}) # Don't need this anymore I think? self.actions[act_type] = AbstractAction._registry[act_type] self.action_map: Dict[int, Tuple[str, Dict]] = {} @@ -237,8 +125,8 @@ class ActionManager: def form_request(self, action_identifier: str, action_options: Dict) -> RequestFormat: """Take action in CAOS format and use the execution definition to change it into PrimAITE request format.""" - act_obj = self.actions[action_identifier] - return act_obj.form_request(action_options) + act_obj = self.actions[action_identifier].from_config(config=action_options) + return act_obj.form_request(config=act_obj.ConfigSchema) @property def space(self) -> spaces.Space: diff --git a/src/primaite/game/agent/actions/network.py b/src/primaite/game/agent/actions/network.py index 63eff218..af3793a2 100644 --- a/src/primaite/game/agent/actions/network.py +++ b/src/primaite/game/agent/actions/network.py @@ -24,12 +24,21 @@ class NetworkPortAbstractAction(AbstractAction, identifier="network_port_abstrac """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" if config.target_nodename is None or config.port_id is None: return ["do_nothing"] - return ["network", "node", config.target_nodename, "network_interface", config.port_id, cls.verb] + return [ + "network", + "node", + config.target_nodename, + "network_interface", + config.port_id, + cls.model_fields["verb"].default, + ] class NetworkPortEnableAction(NetworkPortAbstractAction, identifier="network_port_enable"): """Action which enables are port on a router or a firewall.""" + verb: str = "enable" + class ConfigSchema(AbstractAction.ConfigSchema): """Configuration schema for NetworkPortEnableAction.""" @@ -39,6 +48,8 @@ class NetworkPortEnableAction(NetworkPortAbstractAction, identifier="network_por class NetworkPortDisableAction(NetworkPortAbstractAction, identifier="network_port_disable"): """Action which disables are port on a router or a firewall.""" + verb: str = "disable" + class ConfigSchema(AbstractAction.ConfigSchema): """Configuration schema for NetworkPortDisableAction.""" diff --git a/src/primaite/game/agent/actions/node.py b/src/primaite/game/agent/actions/node.py index f95ba6df..29833b15 100644 --- a/src/primaite/game/agent/actions/node.py +++ b/src/primaite/game/agent/actions/node.py @@ -101,7 +101,8 @@ class NodeNMAPPingScanAction(NodeNMAPAbstractAction, identifier="node_nmap_ping_ class NodeNMAPPortScanAction(NodeNMAPAbstractAction, identifier="node_nmap_port_scan"): """Action which performs an NMAP port scan.""" - class ConfigSchema(AbstractAction.ConfigSchema): + class ConfigSchema(NodeNMAPAbstractAction.ConfigSchema): + source_node: str target_protocol: Optional[Union[str, List[str]]] = (None,) target_port: Optional[Union[str, List[str]]] = (None,) show: Optional[bool] = (False,) diff --git a/src/primaite/game/agent/actions/service.py b/src/primaite/game/agent/actions/service.py index bccfaba2..dbdd57d3 100644 --- a/src/primaite/game/agent/actions/service.py +++ b/src/primaite/game/agent/actions/service.py @@ -32,12 +32,14 @@ class NodeServiceAbstractAction(AbstractAction, identifier="node_service_abstrac @classmethod def form_request(cls, config: ConfigSchema) -> RequestFormat: """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - return ["network", "node", config.node_name, "service", config.service_name, cls.verb] + return ["network", "node", config.node_name, "service", config.service_name, cls.model_fields["verb"].default] class NodeServiceScanAction(NodeServiceAbstractAction, identifier="node_service_scan"): """Action which scans a service.""" + verb: str = "scan" + class ConfigSchema(NodeServiceAbstractAction.ConfigSchema): """Configuration Schema for NodeServiceScanAction.""" @@ -47,6 +49,8 @@ class NodeServiceScanAction(NodeServiceAbstractAction, identifier="node_service_ class NodeServiceStopAction(NodeServiceAbstractAction, identifier="node_service_stop"): """Action which stops a service.""" + verb: str = "stop" + class ConfigSchema(NodeServiceAbstractAction.ConfigSchema): """Configuration Schema for NodeServiceStopAction.""" @@ -56,6 +60,8 @@ class NodeServiceStopAction(NodeServiceAbstractAction, identifier="node_service_ class NodeServiceStartAction(NodeServiceAbstractAction, identifier="node_service_start"): """Action which starts a service.""" + verb: str = "start" + class ConfigSchema(NodeServiceAbstractAction.ConfigSchema): """Configuration Schema for NodeServiceStartAction.""" @@ -65,6 +71,8 @@ class NodeServiceStartAction(NodeServiceAbstractAction, identifier="node_service class NodeServicePauseAction(NodeServiceAbstractAction, identifier="node_service_pause"): """Action which pauses a service.""" + verb: str = "pause" + class ConfigSchema(NodeServiceAbstractAction.ConfigSchema): """Configuration Schema for NodeServicePauseAction.""" @@ -74,6 +82,8 @@ class NodeServicePauseAction(NodeServiceAbstractAction, identifier="node_service class NodeServiceResumeAction(NodeServiceAbstractAction, identifier="node_service_resume"): """Action which resumes a service.""" + verb: str = "resume" + class ConfigSchema(NodeServiceAbstractAction.ConfigSchema): """Configuration Schema for NodeServiceResumeAction.""" @@ -83,6 +93,8 @@ class NodeServiceResumeAction(NodeServiceAbstractAction, identifier="node_servic class NodeServiceRestartAction(NodeServiceAbstractAction, identifier="node_service_restart"): """Action which restarts a service.""" + verb: str = "restart" + class ConfigSchema(NodeServiceAbstractAction.ConfigSchema): """Configuration Schema for NodeServiceRestartAction.""" @@ -92,6 +104,8 @@ class NodeServiceRestartAction(NodeServiceAbstractAction, identifier="node_servi class NodeServiceDisableAction(NodeServiceAbstractAction, identifier="node_service_disable"): """Action which disables a service.""" + verb: str = "disable" + class ConfigSchema(NodeServiceAbstractAction.ConfigSchema): """Configuration Schema for NodeServiceDisableAction.""" @@ -101,6 +115,8 @@ class NodeServiceDisableAction(NodeServiceAbstractAction, identifier="node_servi class NodeServiceEnableAction(NodeServiceAbstractAction, identifier="node_service_enable"): """Action which enables a service.""" + verb: str = "enable" + class ConfigSchema(NodeServiceAbstractAction.ConfigSchema): """Configuration Schema for NodeServiceEnableAction.""" @@ -110,6 +126,8 @@ class NodeServiceEnableAction(NodeServiceAbstractAction, identifier="node_servic class NodeServiceFixAction(NodeServiceAbstractAction, identifier="node_service_fix"): """Action which fixes a service.""" + verb: str = "fix" + class ConfigSchema(NodeServiceAbstractAction.ConfigSchema): """Configuration Schema for NodeServiceFixAction.""" diff --git a/src/primaite/game/agent/actions/session.py b/src/primaite/game/agent/actions/session.py index f77a85b1..76b97cbd 100644 --- a/src/primaite/game/agent/actions/session.py +++ b/src/primaite/game/agent/actions/session.py @@ -30,6 +30,9 @@ class NodeSessionAbstractAction(AbstractAction, identifier="node_session_abstrac class NodeSessionsRemoteLoginAction(NodeSessionAbstractAction, identifier="node_session_remote_login"): """Action which performs a remote session login.""" + username: str + password: str + class ConfigSchema(NodeSessionAbstractAction.ConfigSchema): """Configuration schema for NodeSessionsRemoteLoginAction.""" @@ -54,7 +57,7 @@ class NodeSessionsRemoteLoginAction(NodeSessionAbstractAction, identifier="node_ ] -class NodeSessionsRemoteLogoutAction(NodeSessionAbstractAction, identifier="node_session_remote_logout"): +class NodeSessionsRemoteLogoutAction(NodeSessionAbstractAction, identifier="node_session_remote_logoff"): """Action which performs a remote session logout.""" class ConfigSchema(NodeSessionAbstractAction.ConfigSchema): @@ -68,3 +71,33 @@ class NodeSessionsRemoteLogoutAction(NodeSessionAbstractAction, identifier="node if config.node_name is None or config.remote_ip is None: return ["do_nothing"] return ["network", "node", config.node_name, "service", "Terminal", "remote_logoff", config.remote_ip] + + +class NodeAccountsChangePasswordAction(NodeSessionAbstractAction, identifier="node_accounts_change_password"): + """Action which changes the password for a user.""" + + username: str + current_password: str + new_password: str + + class ConfigSchema(NodeSessionAbstractAction.ConfigSchema): + """Configuration schema for NodeAccountsChangePasswordAction.""" + + username: str + current_password: str + new_password: str + + @classmethod + def form_request(cls, config: ConfigSchema) -> RequestFormat: + """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" + return [ + "network", + "node", + config.node_name, + "service", + "UserManager", + "change_password", + config.username, + config.current_password, + cls.new_password, + ] diff --git a/tests/assets/configs/firewall_actions_network.yaml b/tests/assets/configs/firewall_actions_network.yaml index 2292616d..18e27de7 100644 --- a/tests/assets/configs/firewall_actions_network.yaml +++ b/tests/assets/configs/firewall_actions_network.yaml @@ -96,8 +96,8 @@ agents: action_space: action_list: - - type: DONOTHING - - type: FIREWALL_ACL_ADDRULE + - type: do_nothing + - type: FIREWALL_ACL_ADDRULE firewall_acl_add_rule - type: FIREWALL_ACL_REMOVERULE - type: NETWORK_PORT_DISABLE - type: NETWORK_PORT_ENABLE diff --git a/tests/assets/configs/nmap_network_service_recon_red_agent_config.yaml b/tests/assets/configs/nmap_network_service_recon_red_agent_config.yaml index c5508f13..ec50ecdf 100644 --- a/tests/assets/configs/nmap_network_service_recon_red_agent_config.yaml +++ b/tests/assets/configs/nmap_network_service_recon_red_agent_config.yaml @@ -34,15 +34,16 @@ agents: max_services_per_node: 1 max_applications_per_node: 1 action_list: - - type: NODE_NMAP_NETWORK_SERVICE_RECON + - type: node_network_service_recon action_map: 0: - action: NODE_NMAP_NETWORK_SERVICE_RECON + action: node_network_service_recon options: source_node: client_1 target_ip_address: 192.168.10.0/24 target_port: 80 target_protocol: tcp + show: false reward_function: reward_components: diff --git a/tests/assets/configs/nmap_ping_scan_red_agent_config.yaml b/tests/assets/configs/nmap_ping_scan_red_agent_config.yaml index 33ba3d19..eb7b6752 100644 --- a/tests/assets/configs/nmap_ping_scan_red_agent_config.yaml +++ b/tests/assets/configs/nmap_ping_scan_red_agent_config.yaml @@ -34,13 +34,14 @@ agents: max_services_per_node: 1 max_applications_per_node: 1 action_list: - - type: NODE_NMAP_PING_SCAN + - type: node_nmap_ping_scan action_map: 0: - action: NODE_NMAP_PING_SCAN + action: node_nmap_ping_scan options: - source_node: client_1 + node_name: client_1 target_ip_address: 192.168.1.0/24 + show: False reward_function: reward_components: diff --git a/tests/assets/configs/nmap_port_scan_red_agent_config.yaml b/tests/assets/configs/nmap_port_scan_red_agent_config.yaml index 8ed715c1..15e2cb6a 100644 --- a/tests/assets/configs/nmap_port_scan_red_agent_config.yaml +++ b/tests/assets/configs/nmap_port_scan_red_agent_config.yaml @@ -34,19 +34,21 @@ agents: max_services_per_node: 1 max_applications_per_node: 1 action_list: - - type: NODE_NMAP_PORT_SCAN + - type: node_nmap_port_scan action_map: 0: - action: NODE_NMAP_PORT_SCAN + action: node_nmap_port_scan options: source_node: client_1 target_ip_address: 192.168.10.0/24 + target_protocol: tcp target_port: - 21 - 53 - 80 - 123 - 219 + show: false reward_function: reward_components: diff --git a/tests/conftest.py b/tests/conftest.py index 64fe0699..8bfc78a4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -419,54 +419,54 @@ def game_and_agent(): install_stuff_to_sim(sim) actions = [ - {"type": "DONOTHING"}, - {"type": "NODE_SERVICE_SCAN"}, - {"type": "NODE_SERVICE_STOP"}, - {"type": "NODE_SERVICE_START"}, - {"type": "NODE_SERVICE_PAUSE"}, - {"type": "NODE_SERVICE_RESUME"}, - {"type": "NODE_SERVICE_RESTART"}, - {"type": "NODE_SERVICE_DISABLE"}, - {"type": "NODE_SERVICE_ENABLE"}, - {"type": "NODE_SERVICE_FIX"}, - {"type": "NODE_APPLICATION_EXECUTE"}, - {"type": "NODE_APPLICATION_SCAN"}, - {"type": "NODE_APPLICATION_CLOSE"}, - {"type": "NODE_APPLICATION_FIX"}, - {"type": "NODE_APPLICATION_INSTALL"}, - {"type": "NODE_APPLICATION_REMOVE"}, - {"type": "NODE_FILE_CREATE"}, - {"type": "NODE_FILE_SCAN"}, - {"type": "NODE_FILE_CHECKHASH"}, - {"type": "NODE_FILE_DELETE"}, - {"type": "NODE_FILE_REPAIR"}, - {"type": "NODE_FILE_RESTORE"}, - {"type": "NODE_FILE_CORRUPT"}, - {"type": "NODE_FILE_ACCESS"}, - {"type": "NODE_FOLDER_CREATE"}, - {"type": "NODE_FOLDER_SCAN"}, - {"type": "NODE_FOLDER_CHECKHASH"}, - {"type": "NODE_FOLDER_REPAIR"}, - {"type": "NODE_FOLDER_RESTORE"}, - {"type": "NODE_OS_SCAN"}, - {"type": "NODE_SHUTDOWN"}, - {"type": "NODE_STARTUP"}, - {"type": "NODE_RESET"}, - {"type": "ROUTER_ACL_ADDRULE"}, - {"type": "ROUTER_ACL_REMOVERULE"}, - {"type": "HOST_NIC_ENABLE"}, - {"type": "HOST_NIC_DISABLE"}, - {"type": "NETWORK_PORT_ENABLE"}, - {"type": "NETWORK_PORT_DISABLE"}, - {"type": "CONFIGURE_C2_BEACON"}, - {"type": "C2_SERVER_RANSOMWARE_LAUNCH"}, - {"type": "C2_SERVER_RANSOMWARE_CONFIGURE"}, - {"type": "C2_SERVER_TERMINAL_COMMAND"}, - {"type": "C2_SERVER_DATA_EXFILTRATE"}, - {"type": "NODE_ACCOUNTS_CHANGE_PASSWORD"}, - {"type": "SSH_TO_REMOTE"}, - {"type": "SESSIONS_REMOTE_LOGOFF"}, - {"type": "NODE_SEND_REMOTE_COMMAND"}, + {"type": "do_nothing"}, + {"type": "node_service_scan"}, + {"type": "node_service_stop"}, + {"type": "node_service_start"}, + {"type": "node_service_pause"}, + {"type": "node_service_resume"}, + {"type": "node_service_restart"}, + {"type": "node_service_disable"}, + {"type": "node_service_enable"}, + {"type": "node_service_fix"}, + {"type": "node_application_execute"}, + {"type": "node_application_scan"}, + {"type": "node_application_close"}, + {"type": "node_application_fix"}, + {"type": "node_application_install"}, + {"type": "node_application_remove"}, + {"type": "node_file_create"}, + {"type": "node_file_scan"}, + {"type": "node_file_checkhash"}, + {"type": "node_file_delete"}, + {"type": "node_file_repair"}, + {"type": "node_file_restore"}, + {"type": "node_file_corrupt"}, + {"type": "node_file_access"}, + {"type": "node_folder_create"}, + {"type": "node_folder_scan"}, + {"type": "node_folder_checkhash"}, + {"type": "node_folder_repair"}, + {"type": "node_folder_restore"}, + {"type": "node_os_scan"}, + {"type": "node_shutdown"}, + {"type": "node_startup"}, + {"type": "node_reset"}, + {"type": "router_acl_add_rule"}, + {"type": "router_acl_remove_rule"}, + {"type": "host_nic_enable"}, + {"type": "host_nic_disable"}, + {"type": "network_port_enable"}, + {"type": "network_port_disable"}, + {"type": "configure_c2_beacon"}, + {"type": "c2_server_ransomware_launch"}, + {"type": "c2_server_ransomware_configure"}, + {"type": "c2_server_terminal_command"}, + {"type": "c2_server_data_exfiltrate"}, + {"type": "node_accounts_change_password"}, + {"type": "node_session_remote_login"}, + {"type": "node_session_remote_logoff"}, + {"type": "node_send_remote_command"}, ] action_space = ActionManager( diff --git a/tests/integration_tests/game_layer/test_actions.py b/tests/integration_tests/game_layer/test_actions.py index e03a7d26..21aa31de 100644 --- a/tests/integration_tests/game_layer/test_actions.py +++ b/tests/integration_tests/game_layer/test_actions.py @@ -35,7 +35,7 @@ def test_do_nothing_integration(game_and_agent: Tuple[PrimaiteGame, ProxyAgent]) """Test that the DoNothingAction can form a request and that it is accepted by the simulation.""" game, agent = game_and_agent - action = ("DONOTHING", {}) + action = ("do_nothing", {}) agent.store_action(action) game.step() @@ -56,7 +56,7 @@ def test_node_service_scan_integration(game_and_agent: Tuple[PrimaiteGame, Proxy assert svc.health_state_visible == SoftwareHealthState.UNUSED # 2: Scan and check that the visible state is now correct - action = ("NODE_SERVICE_SCAN", {"node_id": 1, "service_id": 0}) + action = ("node_service_scan", {"node_name": "server_1", "service_name": "DNSServer"}) agent.store_action(action) game.step() assert svc.health_state_actual == SoftwareHealthState.GOOD @@ -67,7 +67,7 @@ def test_node_service_scan_integration(game_and_agent: Tuple[PrimaiteGame, Proxy assert svc.health_state_visible == SoftwareHealthState.GOOD # 4: Scan and check that the visible state is now correct - action = ("NODE_SERVICE_SCAN", {"node_id": 1, "service_id": 0}) + action = ("node_service_scan", {"node_name": "server_1", "service_name": "DNSServer"}) agent.store_action(action) game.step() assert svc.health_state_actual == SoftwareHealthState.COMPROMISED @@ -88,7 +88,7 @@ def test_node_service_fix_integration(game_and_agent: Tuple[PrimaiteGame, ProxyA svc.health_state_actual = SoftwareHealthState.COMPROMISED # 2: Apply a patch action - action = ("NODE_SERVICE_FIX", {"node_id": 1, "service_id": 0}) + action = ("node_service_fix", {"node_name": "server_1", "service_name": "DNSServer"}) agent.store_action(action) game.step() @@ -96,7 +96,7 @@ def test_node_service_fix_integration(game_and_agent: Tuple[PrimaiteGame, ProxyA assert svc.health_state_actual == SoftwareHealthState.FIXING # 4: perform a few do-nothing steps and check that the service is now in the good state - action = ("DONOTHING", {}) + action = ("do_nothing", {}) agent.store_action(action) game.step() assert svc.health_state_actual == SoftwareHealthState.GOOD @@ -121,7 +121,7 @@ def test_router_acl_addrule_integration(game_and_agent: Tuple[PrimaiteGame, Prox # 2: Add a rule to block client 1 from reaching server 2 on router action = ( - "ROUTER_ACL_ADDRULE", + "router_acl_add_rule", { "target_router": "router", "position": 4, # 4th rule @@ -130,7 +130,7 @@ def test_router_acl_addrule_integration(game_and_agent: Tuple[PrimaiteGame, Prox "dest_ip_id": 6, # 10.0.2.3 (server_2) "dest_port_id": 1, # ALL "source_port_id": 1, # ALL - "protocol_id": 1, # ALL + "protocol_name": "ALL", # ALL "source_wildcard_id": 0, "dest_wildcard_id": 0, }, @@ -186,7 +186,7 @@ def test_router_acl_removerule_integration(game_and_agent: Tuple[PrimaiteGame, P # 2: Remove rule that allows HTTP traffic across the network action = ( - "ROUTER_ACL_REMOVERULE", + "router_acl_remove_rule", { "target_router": "router", "position": 3, # 4th rule @@ -219,10 +219,10 @@ def test_host_nic_disable_integration(game_and_agent: Tuple[PrimaiteGame, ProxyA # 2: Disable the NIC on client_1 action = ( - "HOST_NIC_DISABLE", + "host_nic_disable", { - "node_id": 0, # client_1 - "nic_id": 0, # the only nic (eth-1) + "node_name": "client_1", # client_1 + "nic_num": 1, # the only nic (eth-1) }, ) agent.store_action(action) @@ -250,10 +250,10 @@ def test_host_nic_enable_integration(game_and_agent: Tuple[PrimaiteGame, ProxyAg # 2: Use action to enable nic action = ( - "HOST_NIC_ENABLE", + "host_nic_enable", { - "node_id": 0, # client_1 - "nic_id": 0, # the only nic (eth-1) + "node_name": "client_1", # client_1 + "nic_num": 1, # the only nic (eth-1) }, ) agent.store_action(action) @@ -277,11 +277,11 @@ def test_node_file_scan_integration(game_and_agent: Tuple[PrimaiteGame, ProxyAge # 2: perform a scan and make sure nothing has changed action = ( - "NODE_FILE_SCAN", + "node_file_scan", { - "node_id": 0, # client_1, - "folder_id": 0, # downloads, - "file_id": 0, # cat.png + "node_name": "client_1", # client_1, + "folder_name": "downloads", # downloads, + "file_name": "cat.png", # cat.png }, ) agent.store_action(action) @@ -314,11 +314,11 @@ def test_node_file_delete_integration(game_and_agent: Tuple[PrimaiteGame, ProxyA # 2: delete the file action = ( - "NODE_FILE_DELETE", + "node_file_delete", { - "node_id": 0, # client_1 - "folder_id": 0, # downloads - "file_id": 0, # cat.png + "node_name": "client_1", # client_1 + "folder_name": "downloads", # downloads + "file_name": "cat.png", # cat.png }, ) agent.store_action(action) @@ -334,15 +334,11 @@ def test_node_file_create(game_and_agent: Tuple[PrimaiteGame, ProxyAgent]): """Test that a file is created.""" game, agent = game_and_agent - client_1 = game.simulation.network.get_node_by_hostname("client_1") # + client_1 = game.simulation.network.get_node_by_hostname("client_1") action = ( - "NODE_FILE_CREATE", - { - "node_id": 0, - "folder_name": "test", - "file_name": "file.txt", - }, + "node_file_create", + {"node_name": "client_1", "folder_name": "test", "file_name": "file.txt", "force": "False"}, ) agent.store_action(action) game.step() @@ -357,9 +353,9 @@ def test_node_file_access(game_and_agent: Tuple[PrimaiteGame, ProxyAgent]): client_1 = game.simulation.network.get_node_by_hostname("client_1") # action = ( - "NODE_FILE_CREATE", + "node_file_create", { - "node_id": 0, + "node_name": "client_1", "folder_name": "test", "file_name": "file.txt", }, @@ -370,9 +366,9 @@ def test_node_file_access(game_and_agent: Tuple[PrimaiteGame, ProxyAgent]): assert client_1.file_system.get_file(folder_name="test", file_name="file.txt").num_access == 0 action = ( - "NODE_FILE_ACCESS", + "node_file_access", { - "node_id": 0, + "node_name": "client_1", "folder_name": "test", "file_name": "file.txt", }, @@ -390,9 +386,9 @@ def test_node_folder_create(game_and_agent: Tuple[PrimaiteGame, ProxyAgent]): client_1 = game.simulation.network.get_node_by_hostname("client_1") # action = ( - "NODE_FOLDER_CREATE", + "node_folder_create", { - "node_id": 0, + "node_name": "client_1", "folder_name": "test", }, ) @@ -418,7 +414,7 @@ def test_network_router_port_disable_integration(game_and_agent: Tuple[PrimaiteG # 2: Disable the NIC on client_1 action = ( - "NETWORK_PORT_DISABLE", + "network_port_disable", { "target_nodename": "router", # router "port_id": 1, # port 1 @@ -450,7 +446,7 @@ def test_network_router_port_enable_integration(game_and_agent: Tuple[PrimaiteGa # 2: Use action to enable port action = ( - "NETWORK_PORT_ENABLE", + "network_port_enable", { "target_nodename": "router", # router "port_id": 1, # port 1 @@ -480,7 +476,7 @@ def test_node_application_scan_integration(game_and_agent: Tuple[PrimaiteGame, P assert browser.health_state_visible == SoftwareHealthState.UNUSED # 2: Scan and check that the visible state is now correct - action = ("NODE_APPLICATION_SCAN", {"node_id": 0, "application_id": 0}) + action = ("node_application_scan", {"node_name": "client_1", "application_name": "WebBrowser"}) agent.store_action(action) game.step() assert browser.health_state_actual == SoftwareHealthState.GOOD @@ -491,7 +487,7 @@ def test_node_application_scan_integration(game_and_agent: Tuple[PrimaiteGame, P assert browser.health_state_visible == SoftwareHealthState.GOOD # 4: Scan and check that the visible state is now correct - action = ("NODE_APPLICATION_SCAN", {"node_id": 0, "application_id": 0}) + action = ("node_application_scan", {"node_name": "client_1", "application_name": "WebBrowser"}) agent.store_action(action) game.step() assert browser.health_state_actual == SoftwareHealthState.COMPROMISED @@ -512,7 +508,7 @@ def test_node_application_fix_integration(game_and_agent: Tuple[PrimaiteGame, Pr browser.health_state_actual = SoftwareHealthState.COMPROMISED # 2: Apply a fix action - action = ("NODE_APPLICATION_FIX", {"node_id": 0, "application_id": 0}) + action = ("node_application_fix", {"node_name": "client_1", "application_name": "WebBrowser"}) agent.store_action(action) game.step() @@ -520,7 +516,7 @@ def test_node_application_fix_integration(game_and_agent: Tuple[PrimaiteGame, Pr assert browser.health_state_actual == SoftwareHealthState.FIXING # 4: perform a few do-nothing steps and check that the application is now in the good state - action = ("DONOTHING", {}) + action = ("do_nothing", {}) agent.store_action(action) game.step() assert browser.health_state_actual == SoftwareHealthState.GOOD @@ -538,7 +534,7 @@ def test_node_application_close_integration(game_and_agent: Tuple[PrimaiteGame, assert browser.operating_state == ApplicationOperatingState.RUNNING # 2: Apply a close action - action = ("NODE_APPLICATION_CLOSE", {"node_id": 0, "application_id": 0}) + action = ("node_application_close", {"node_name": "client_1", "application_name": "WebBrowser"}) agent.store_action(action) game.step() @@ -549,7 +545,7 @@ def test_node_application_install_and_uninstall_integration(game_and_agent: Tupl """Test that the NodeApplicationInstallAction and NodeApplicationRemoveAction can form a request and that it is accepted by the simulation. - When you initiate a install action, the Application will be installed and configured on the node. + When you initiate an install action, the Application will be installed and configured on the node. The remove action will uninstall the application from the node.""" game, agent = game_and_agent @@ -557,13 +553,13 @@ def test_node_application_install_and_uninstall_integration(game_and_agent: Tupl assert client_1.software_manager.software.get("DoSBot") is None - action = ("NODE_APPLICATION_INSTALL", {"node_id": 0, "application_name": "DoSBot"}) + action = ("node_application_install", {"node_name": "client_1", "application_name": "DoSBot"}) agent.store_action(action) game.step() assert client_1.software_manager.software.get("DoSBot") is not None - action = ("NODE_APPLICATION_REMOVE", {"node_id": 0, "application_name": "DoSBot"}) + action = ("node_application_remove", {"node_name": "client_1", "application_name": "DoSBot"}) agent.store_action(action) game.step() diff --git a/tests/unit_tests/_primaite/_game/_agent/test_probabilistic_agent.py b/tests/unit_tests/_primaite/_game/_agent/test_probabilistic_agent.py index ec18f1fb..2fd2da0c 100644 --- a/tests/unit_tests/_primaite/_game/_agent/test_probabilistic_agent.py +++ b/tests/unit_tests/_primaite/_game/_agent/test_probabilistic_agent.py @@ -27,9 +27,9 @@ def test_probabilistic_agent(): action_space = ActionManager( actions=[ - {"type": "DONOTHING"}, - {"type": "NODE_APPLICATION_EXECUTE"}, - {"type": "NODE_FILE_DELETE"}, + {"type": "do_nothing"}, + {"type": "node_application_execute"}, + {"type": "node_file_delete"}, ], nodes=[ { @@ -47,9 +47,15 @@ def test_probabilistic_agent(): protocols=["TCP", "UDP", "ICMP"], ports=["HTTP", "DNS", "ARP"], act_map={ - 0: {"action": "DONOTHING", "options": {}}, - 1: {"action": "NODE_APPLICATION_EXECUTE", "options": {"node_id": 0, "application_id": 0}}, - 2: {"action": "NODE_FILE_DELETE", "options": {"node_id": 0, "folder_id": 0, "file_id": 0}}, + 0: {"action": "do_nothing", "options": {}}, + 1: { + "action": "node_application_execute", + "options": {"node_name": "client_1", "application_name": "WebBrowser"}, + }, + 2: { + "action": "node_file_delete", + "options": {"node_name": "client_1", "folder_name": "downloads", "file_name": "cat.png"}, + }, }, ) observation_space = ObservationManager(NestedObservation(components={})) @@ -70,11 +76,11 @@ def test_probabilistic_agent(): node_file_delete_count = 0 for _ in range(N_TRIALS): a = pa.get_action(0) - if a == ("DONOTHING", {}): + if a == ("do_nothing", {}): do_nothing_count += 1 - elif a == ("NODE_APPLICATION_EXECUTE", {"node_id": 0, "application_id": 0}): + elif a == ("node_application_execute", {"node_name": "client_1", "application_name": "WebBrowser"}): node_application_execute_count += 1 - elif a == ("NODE_FILE_DELETE", {"node_id": 0, "folder_id": 0, "file_id": 0}): + elif a == ("node_file_delete", {"node_name": "client_1", "folder_name": "downloads", "file_name": "cat.png"}): node_file_delete_count += 1 else: raise AssertionError("Probabilistic agent produced an unexpected action.") From eb827f7e0a98b8c4dc9a94dc0e739a7c213d1d9f Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Thu, 31 Oct 2024 14:42:26 +0000 Subject: [PATCH 26/58] #2913: How-To guide initial commit. --- .../how_to_guides/extensible_rewards.rst | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 docs/source/how_to_guides/extensible_rewards.rst diff --git a/docs/source/how_to_guides/extensible_rewards.rst b/docs/source/how_to_guides/extensible_rewards.rst new file mode 100644 index 00000000..3505d66c --- /dev/null +++ b/docs/source/how_to_guides/extensible_rewards.rst @@ -0,0 +1,72 @@ +.. only:: comment + + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + +.. _about: + +Extensible Rewards +****************** + +Changes to reward class structure. +================================== + +Reward classes are inherited from AbstractReward (a sub-class of Pydantic's BaseModel). +Within the reward class is a ConfigSchema class responsible for ensuring config file data is in the +correct format. The `.from_config()` method is generally unchanged. + +Inheriting from `BaseModel` removes the need for an `__init__` method bu means that object +attributes need to be passed by keyword. + +.. code:: Python + +class AbstractReward(BaseModel): + """Base class for reward function components.""" + + class ConfigSchema(BaseModel, ABC): + """Config schema for AbstractReward.""" + + type: str + + _registry: ClassVar[Dict[str, Type["AbstractReward"]]] = {} + + def __init_subclass__(cls, identifier: str, **kwargs: Any) -> None: + super().__init_subclass__(**kwargs) + if identifier in cls._registry: + raise ValueError(f"Duplicate node adder {identifier}") + cls._registry[identifier] = cls + + @classmethod + def from_config(cls, config: Dict) -> "AbstractReward": + """Create a reward function component from a config dictionary. + + :param config: dict of options for the reward component's constructor + :type config: dict + :return: The reward component. + :rtype: AbstractReward + """ + if config["type"] not in cls._registry: + raise ValueError(f"Invalid reward type {config['type']}") + adder_class = cls._registry[config["type"]] + adder_class.add_nodes_to_net(config=adder_class.ConfigSchema(**config)) + return cls + + @abstractmethod + def calculate(self, state: Dict, last_action_response: "AgentHistoryItem") -> float: + """Calculate the reward for the current state. + + :param state: Current simulation state + :type state: Dict + :param last_action_response: Current agent history state + :type last_action_response: AgentHistoryItem state + :return: Reward value + :rtype: float + """ + return 0.0 + + +Changes to YAML file. +===================== +.. code:: YAML + + There's no longer a need to provide a `dns_server` as an option in the simulation section + of the config file. \ No newline at end of file From 6b29362bf955ece37b39b31d34ee189231f9b5e5 Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Thu, 31 Oct 2024 14:42:50 +0000 Subject: [PATCH 27/58] #2913: Tidy up config files. --- tests/assets/configs/fix_duration_one_item.yaml | 2 -- tests/assets/configs/software_fix_duration.yaml | 1 - 2 files changed, 3 deletions(-) diff --git a/tests/assets/configs/fix_duration_one_item.yaml b/tests/assets/configs/fix_duration_one_item.yaml index 4163bcfd..c74590a1 100644 --- a/tests/assets/configs/fix_duration_one_item.yaml +++ b/tests/assets/configs/fix_duration_one_item.yaml @@ -230,8 +230,6 @@ simulation: server_password: arcd services: - type: DNSClient - # options: - # dns_server: 192.168.1.10 links: - endpoint_a_hostname: switch_1 diff --git a/tests/assets/configs/software_fix_duration.yaml b/tests/assets/configs/software_fix_duration.yaml index 2b72e85f..6a705b37 100644 --- a/tests/assets/configs/software_fix_duration.yaml +++ b/tests/assets/configs/software_fix_duration.yaml @@ -209,7 +209,6 @@ simulation: services: - type: DNSClient options: - # dns_server: 192.168.1.10 fix_duration: 3 - type: DNSServer options: From b849ea6312c21366256fc297297017112dab1d9b Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Mon, 4 Nov 2024 17:41:43 +0000 Subject: [PATCH 28/58] #2913: Remove from_config() and refactor (WIP). --- src/primaite/game/agent/rewards.py | 167 +++++------------- .../game_layer/test_rewards.py | 11 +- 2 files changed, 48 insertions(+), 130 deletions(-) diff --git a/src/primaite/game/agent/rewards.py b/src/primaite/game/agent/rewards.py index 2386bed5..03764e4b 100644 --- a/src/primaite/game/agent/rewards.py +++ b/src/primaite/game/agent/rewards.py @@ -46,6 +46,14 @@ WhereType = Optional[Iterable[Union[str, int]]] class AbstractReward(BaseModel): """Base class for reward function components.""" + config: "AbstractReward.ConfigSchema" + + # def __init__(self, schema_name, **kwargs): + # super.__init__(self, **kwargs) + # # Create ConfigSchema class + # self.config_class = type(schema_name, (BaseModel, ABC), **kwargs) + # self.config = self.config_class() + class ConfigSchema(BaseModel, ABC): """Config schema for AbstractReward.""" @@ -56,7 +64,7 @@ class AbstractReward(BaseModel): def __init_subclass__(cls, identifier: str, **kwargs: Any) -> None: super().__init_subclass__(**kwargs) if identifier in cls._registry: - raise ValueError(f"Duplicate node adder {identifier}") + raise ValueError(f"Duplicate reward {identifier}") cls._registry[identifier] = cls @classmethod @@ -70,9 +78,10 @@ class AbstractReward(BaseModel): """ if config["type"] not in cls._registry: raise ValueError(f"Invalid reward type {config['type']}") - adder_class = cls._registry[config["type"]] - adder_class.add_nodes_to_net(config=adder_class.ConfigSchema(**config)) - return cls + reward_class = cls._registry[config["type"]] + reward_config = reward_class.ConfigSchema(**config) + reward_class(config=reward_config) + return reward_class @abstractmethod def calculate(self, state: Dict, last_action_response: "AgentHistoryItem") -> float: @@ -103,30 +112,18 @@ class DummyReward(AbstractReward, identifier="DummyReward"): """ return 0.0 - @classmethod - def from_config(cls, config: dict) -> "DummyReward": - """Create a reward function component from a config dictionary. - - :param config: dict of options for the reward component's constructor. Should be empty. - :type config: dict - :return: The reward component. - :rtype: DummyReward - """ - return cls() - class DatabaseFileIntegrity(AbstractReward, identifier="DatabaseFileIntegrity"): """Reward function component which rewards the agent for maintaining the integrity of a database file.""" - node_hostname: str - folder_name: str - file_name: str + config: "DatabaseFileIntegrity.ConfigSchema" location_in_state: List[str] = [""] reward: float = 0.0 class ConfigSchema(AbstractReward.ConfigSchema): """ConfigSchema for DatabaseFileIntegrity.""" + type: str = "DatabaseFileIntegrity" node_hostname: str folder_name: str file_name: str @@ -144,12 +141,12 @@ class DatabaseFileIntegrity(AbstractReward, identifier="DatabaseFileIntegrity"): self.location_in_state = [ "network", "nodes", - self.node_hostname, + self.config.node_hostname, "file_system", "folders", - self.folder_name, + self.config.folder_name, "files", - self.file_name, + self.config.file_name, ] database_file_state = access_from_nested_dict(state, self.location_in_state) @@ -168,38 +165,18 @@ class DatabaseFileIntegrity(AbstractReward, identifier="DatabaseFileIntegrity"): else: return 0 - @classmethod - def from_config(cls, config: Dict) -> "DatabaseFileIntegrity": - """Create a reward function component from a config dictionary. - - :param config: dict of options for the reward component's constructor - :type config: Dict - :return: The reward component. - :rtype: DatabaseFileIntegrity - """ - node_hostname = config.get("node_hostname") - folder_name = config.get("folder_name") - file_name = config.get("file_name") - if not (node_hostname and folder_name and file_name): - msg = f"{cls.__name__} could not be initialised with parameters {config}" - _LOGGER.error(msg) - raise ValueError(msg) - - return cls(node_hostname=node_hostname, folder_name=folder_name, file_name=file_name) - class WebServer404Penalty(AbstractReward, identifier="WebServer404Penalty"): """Reward function component which penalises the agent when the web server returns a 404 error.""" - node_hostname: str - service_name: str - sticky: bool = True + config: "WebServer404Penalty.ConfigSchema" location_in_state: List[str] = [""] reward: float = 0.0 class ConfigSchema(AbstractReward.ConfigSchema): """ConfigSchema for WebServer404Penalty.""" + type: str = "WebServer404Penalty" node_hostname: str service_name: str sticky: bool = True @@ -217,9 +194,9 @@ class WebServer404Penalty(AbstractReward, identifier="WebServer404Penalty"): self.location_in_state = [ "network", "nodes", - self.node_hostname, + self.config.node_hostname, "services", - self.service_name, + self.config.service_name, ] web_service_state = access_from_nested_dict(state, self.location_in_state) @@ -242,43 +219,20 @@ class WebServer404Penalty(AbstractReward, identifier="WebServer404Penalty"): return self.reward - @classmethod - def from_config(cls, config: Dict) -> "WebServer404Penalty": - """Create a reward function component from a config dictionary. - - :param config: dict of options for the reward component's constructor - :type config: Dict - :return: The reward component. - :rtype: WebServer404Penalty - """ - node_hostname = config.get("node_hostname") - service_name = config.get("service_name") - if not (node_hostname and service_name): - msg = ( - f"{cls.__name__} could not be initialised from config because node_name and service_ref were not " - "found in reward config." - ) - _LOGGER.warning(msg) - raise ValueError(msg) - sticky = config.get("sticky", True) - - return cls(node_hostname=node_hostname, service_name=service_name, sticky=sticky) - class WebpageUnavailablePenalty(AbstractReward, identifier="WebpageUnavailablePenalty"): """Penalises the agent when the web browser fails to fetch a webpage.""" - node_hostname: str = "" - sticky: bool = True - reward: float = 0.0 - location_in_state: List[str] = [""] + config: "WebpageUnavailablePenalty.ConfigSchema" + reward: float = 0.0 # XXX: Private attribute? + location_in_state: List[str] = [""] # Calculate in __init__()? class ConfigSchema(AbstractReward.ConfigSchema): """ConfigSchema for WebpageUnavailablePenalty.""" + type: str = "WebpageUnavailablePenalty" node_hostname: str = "" sticky: bool = True - reward: float = 0.0 def calculate(self, state: Dict, last_action_response: "AgentHistoryItem") -> float: """ @@ -297,7 +251,7 @@ class WebpageUnavailablePenalty(AbstractReward, identifier="WebpageUnavailablePe self.location_in_state = [ "network", "nodes", - self.node_hostname, + self.config.node_hostname, "applications", "WebBrowser", ] @@ -310,14 +264,14 @@ class WebpageUnavailablePenalty(AbstractReward, identifier="WebpageUnavailablePe request_attempted = last_action_response.request == [ "network", "node", - self.node_hostname, + self.config.node_hostname, "application", "WebBrowser", "execute", ] # skip calculating if sticky and no new codes, reusing last step value - if not request_attempted and self.sticky: + if not request_attempted and self.config.sticky: return self.reward if last_action_response.response.status != "success": @@ -339,29 +293,17 @@ class WebpageUnavailablePenalty(AbstractReward, identifier="WebpageUnavailablePe return self.reward - @classmethod - def from_config(cls, config: dict) -> AbstractReward: - """ - Build the reward component object from config. - - :param config: Configuration dictionary. - :type config: Dict - """ - node_hostname = config.get("node_hostname") - sticky = config.get("sticky", True) - return cls(node_hostname=node_hostname, sticky=sticky) - class GreenAdminDatabaseUnreachablePenalty(AbstractReward, identifier="GreenAdminDatabaseUnreachablePenalty"): """Penalises the agent when the green db clients fail to connect to the database.""" - node_hostname: str = "" - sticky: bool = True + config: "GreenAdminDatabaseUnreachablePenalty.ConfigSchema" reward: float = 0.0 class ConfigSchema(AbstractReward.ConfigSchema): """ConfigSchema for GreenAdminDatabaseUnreachablePenalty.""" + type: str = "GreenAdminDatabaseUnreachablePenalty" node_hostname: str sticky: bool = True @@ -383,7 +325,7 @@ class GreenAdminDatabaseUnreachablePenalty(AbstractReward, identifier="GreenAdmi request_attempted = last_action_response.request == [ "network", "node", - self.node_hostname, + self.config.node_hostname, "application", "DatabaseClient", "execute", @@ -392,7 +334,7 @@ class GreenAdminDatabaseUnreachablePenalty(AbstractReward, identifier="GreenAdmi if request_attempted: # if agent makes request, always recalculate fresh value last_action_response.reward_info = {"connection_attempt_status": last_action_response.response.status} self.reward = 1.0 if last_action_response.response.status == "success" else -1.0 - elif not self.sticky: # if no new request and not sticky, set reward to 0 + elif not self.config.sticky: # if no new request and not sticky, set reward to 0 last_action_response.reward_info = {"connection_attempt_status": "n/a"} self.reward = 0.0 else: # if no new request and sticky, reuse reward value from last step @@ -401,27 +343,16 @@ class GreenAdminDatabaseUnreachablePenalty(AbstractReward, identifier="GreenAdmi return self.reward - @classmethod - def from_config(cls, config: Dict) -> AbstractReward: - """ - Build the reward component object from config. - - :param config: Configuration dictionary. - :type config: Dict - """ - node_hostname = config.get("node_hostname") - sticky = config.get("sticky", True) - return cls(node_hostname=node_hostname, sticky=sticky) - class SharedReward(AbstractReward, identifier="SharedReward"): """Adds another agent's reward to the overall reward.""" - agent_name: str + config: "SharedReward.ConfigSchema" class ConfigSchema(AbstractReward.ConfigSchema): """Config schema for SharedReward.""" + type: str = "SharedReward" agent_name: str def default_callback(agent_name: str) -> Never: @@ -447,29 +378,18 @@ class SharedReward(AbstractReward, identifier="SharedReward"): :return: Reward value :rtype: float """ - return self.callback(self.agent_name) - - @classmethod - def from_config(cls, config: Dict) -> "SharedReward": - """ - Build the SharedReward object from config. - - :param config: Configuration dictionary - :type config: Dict - """ - agent_name = config.get("agent_name") - return cls(agent_name=agent_name) + return self.callback(self.config.agent_name) class ActionPenalty(AbstractReward, identifier="ActionPenalty"): """Apply a negative reward when taking any action except DONOTHING.""" - action_penalty: float = -1.0 - do_nothing_penalty: float = 0.0 + config: "ActionPenalty.ConfigSchema" class ConfigSchema(AbstractReward.ConfigSchema): """Config schema for ActionPenalty.""" + type: str = "ActionPenalty" action_penalty: float = -1.0 do_nothing_penalty: float = 0.0 @@ -484,16 +404,9 @@ class ActionPenalty(AbstractReward, identifier="ActionPenalty"): :rtype: float """ if last_action_response.action == "DONOTHING": - return self.do_nothing_penalty + return self.config.do_nothing_penalty else: - return self.action_penalty - - @classmethod - def from_config(cls, config: Dict) -> "ActionPenalty": - """Build the ActionPenalty object from config.""" - action_penalty = config.get("action_penalty", -1.0) - do_nothing_penalty = config.get("do_nothing_penalty", 0.0) - return cls(action_penalty=action_penalty, do_nothing_penalty=do_nothing_penalty) + return self.config.action_penalty class RewardFunction: diff --git a/tests/integration_tests/game_layer/test_rewards.py b/tests/integration_tests/game_layer/test_rewards.py index bf707feb..d4236d1b 100644 --- a/tests/integration_tests/game_layer/test_rewards.py +++ b/tests/integration_tests/game_layer/test_rewards.py @@ -23,7 +23,9 @@ def test_WebpageUnavailablePenalty(game_and_agent): # set up the scenario, configure the web browser to the correct url game, agent = game_and_agent agent: ControlledAgent - comp = WebpageUnavailablePenalty(node_hostname="client_1") + schema = WebpageUnavailablePenalty.ConfigSchema(node_hostname="client_1", sticky=True) + comp = WebpageUnavailablePenalty(config=schema) + client_1 = game.simulation.network.get_node_by_hostname("client_1") browser: WebBrowser = client_1.software_manager.software.get("WebBrowser") browser.run() @@ -74,7 +76,8 @@ def test_uc2_rewards(game_and_agent): ACLAction.PERMIT, src_port=PORT_LOOKUP["POSTGRES_SERVER"], dst_port=PORT_LOOKUP["POSTGRES_SERVER"], position=2 ) - comp = GreenAdminDatabaseUnreachablePenalty(node_hostname="client_1") + schema = GreenAdminDatabaseUnreachablePenalty.ConfigSchema(node_hostname="client_1", sticky=True) + comp = GreenAdminDatabaseUnreachablePenalty(config=schema) request = ["network", "node", "client_1", "application", "DatabaseClient", "execute"] response = game.simulation.apply_request(request) @@ -147,7 +150,9 @@ def test_action_penalty(): """Test that the action penalty is correctly applied when agent performs any action""" # Create an ActionPenalty Reward - Penalty = ActionPenalty(action_penalty=-0.75, do_nothing_penalty=0.125) + schema = ActionPenalty.ConfigSchema(action_penalty=-0.75, do_nothing_penalty=0.125) + # Penalty = ActionPenalty(action_penalty=-0.75, do_nothing_penalty=0.125) + Penalty = ActionPenalty(schema) # Assert that penalty is applied if action isn't DONOTHING reward_value = Penalty.calculate( From 370bcfc47682049cafe238f97e089b5d8b5ea013 Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Wed, 6 Nov 2024 11:35:06 +0000 Subject: [PATCH 29/58] #2913: Make rewards work with config file. --- src/primaite/game/agent/rewards.py | 51 ++++++++----------- src/primaite/game/game.py | 2 +- .../game_layer/test_rewards.py | 2 +- 3 files changed, 23 insertions(+), 32 deletions(-) diff --git a/src/primaite/game/agent/rewards.py b/src/primaite/game/agent/rewards.py index 03764e4b..029597a0 100644 --- a/src/primaite/game/agent/rewards.py +++ b/src/primaite/game/agent/rewards.py @@ -79,9 +79,8 @@ class AbstractReward(BaseModel): if config["type"] not in cls._registry: raise ValueError(f"Invalid reward type {config['type']}") reward_class = cls._registry[config["type"]] - reward_config = reward_class.ConfigSchema(**config) - reward_class(config=reward_config) - return reward_class + reward_obj = reward_class(config=reward_class.ConfigSchema(**config)) + return reward_obj @abstractmethod def calculate(self, state: Dict, last_action_response: "AgentHistoryItem") -> float: @@ -97,7 +96,7 @@ class AbstractReward(BaseModel): return 0.0 -class DummyReward(AbstractReward, identifier="DummyReward"): +class DummyReward(AbstractReward, identifier="DUMMY"): """Dummy reward function component which always returns 0.0.""" def calculate(self, state: Dict, last_action_response: "AgentHistoryItem") -> float: @@ -113,7 +112,7 @@ class DummyReward(AbstractReward, identifier="DummyReward"): return 0.0 -class DatabaseFileIntegrity(AbstractReward, identifier="DatabaseFileIntegrity"): +class DatabaseFileIntegrity(AbstractReward, identifier="DATABASE_FILE_INTEGRITY"): """Reward function component which rewards the agent for maintaining the integrity of a database file.""" config: "DatabaseFileIntegrity.ConfigSchema" @@ -123,7 +122,7 @@ class DatabaseFileIntegrity(AbstractReward, identifier="DatabaseFileIntegrity"): class ConfigSchema(AbstractReward.ConfigSchema): """ConfigSchema for DatabaseFileIntegrity.""" - type: str = "DatabaseFileIntegrity" + type: str = "DATABASE_FILE_INTEGRITY" node_hostname: str folder_name: str file_name: str @@ -166,7 +165,7 @@ class DatabaseFileIntegrity(AbstractReward, identifier="DatabaseFileIntegrity"): return 0 -class WebServer404Penalty(AbstractReward, identifier="WebServer404Penalty"): +class WebServer404Penalty(AbstractReward, identifier="WEB_SERVER_404_PENALTY"): """Reward function component which penalises the agent when the web server returns a 404 error.""" config: "WebServer404Penalty.ConfigSchema" @@ -176,7 +175,7 @@ class WebServer404Penalty(AbstractReward, identifier="WebServer404Penalty"): class ConfigSchema(AbstractReward.ConfigSchema): """ConfigSchema for WebServer404Penalty.""" - type: str = "WebServer404Penalty" + type: str = "WEB_SERVER_404_PENALTY" node_hostname: str service_name: str sticky: bool = True @@ -220,7 +219,7 @@ class WebServer404Penalty(AbstractReward, identifier="WebServer404Penalty"): return self.reward -class WebpageUnavailablePenalty(AbstractReward, identifier="WebpageUnavailablePenalty"): +class WebpageUnavailablePenalty(AbstractReward, identifier="WEBPAGE_UNAVAILABLE_PENALTY"): """Penalises the agent when the web browser fails to fetch a webpage.""" config: "WebpageUnavailablePenalty.ConfigSchema" @@ -230,7 +229,7 @@ class WebpageUnavailablePenalty(AbstractReward, identifier="WebpageUnavailablePe class ConfigSchema(AbstractReward.ConfigSchema): """ConfigSchema for WebpageUnavailablePenalty.""" - type: str = "WebpageUnavailablePenalty" + type: str = "WEBPAGE_UNAVAILABLE_PENALTY" node_hostname: str = "" sticky: bool = True @@ -294,7 +293,7 @@ class WebpageUnavailablePenalty(AbstractReward, identifier="WebpageUnavailablePe return self.reward -class GreenAdminDatabaseUnreachablePenalty(AbstractReward, identifier="GreenAdminDatabaseUnreachablePenalty"): +class GreenAdminDatabaseUnreachablePenalty(AbstractReward, identifier="GREEN_ADMIN_DATABASE_UNREACHABLE_PENALTY"): """Penalises the agent when the green db clients fail to connect to the database.""" config: "GreenAdminDatabaseUnreachablePenalty.ConfigSchema" @@ -303,7 +302,7 @@ class GreenAdminDatabaseUnreachablePenalty(AbstractReward, identifier="GreenAdmi class ConfigSchema(AbstractReward.ConfigSchema): """ConfigSchema for GreenAdminDatabaseUnreachablePenalty.""" - type: str = "GreenAdminDatabaseUnreachablePenalty" + type: str = "GREEN_ADMIN_DATABASE_UNREACHABLE_PENALTY" node_hostname: str sticky: bool = True @@ -344,7 +343,7 @@ class GreenAdminDatabaseUnreachablePenalty(AbstractReward, identifier="GreenAdmi return self.reward -class SharedReward(AbstractReward, identifier="SharedReward"): +class SharedReward(AbstractReward, identifier="SHARED_REWARD"): """Adds another agent's reward to the overall reward.""" config: "SharedReward.ConfigSchema" @@ -352,7 +351,7 @@ class SharedReward(AbstractReward, identifier="SharedReward"): class ConfigSchema(AbstractReward.ConfigSchema): """Config schema for SharedReward.""" - type: str = "SharedReward" + type: str = "SHARED_REWARD" agent_name: str def default_callback(agent_name: str) -> Never: @@ -381,7 +380,7 @@ class SharedReward(AbstractReward, identifier="SharedReward"): return self.callback(self.config.agent_name) -class ActionPenalty(AbstractReward, identifier="ActionPenalty"): +class ActionPenalty(AbstractReward, identifier="ACTION_PENALTY"): """Apply a negative reward when taking any action except DONOTHING.""" config: "ActionPenalty.ConfigSchema" @@ -389,7 +388,7 @@ class ActionPenalty(AbstractReward, identifier="ActionPenalty"): class ConfigSchema(AbstractReward.ConfigSchema): """Config schema for ActionPenalty.""" - type: str = "ActionPenalty" + type: str = "ACTION_PENALTY" action_penalty: float = -1.0 do_nothing_penalty: float = 0.0 @@ -412,17 +411,6 @@ class ActionPenalty(AbstractReward, identifier="ActionPenalty"): class RewardFunction: """Manages the reward function for the agent.""" - rew_class_identifiers: Dict[str, Type[AbstractReward]] = { - "DUMMY": DummyReward, - "DATABASE_FILE_INTEGRITY": DatabaseFileIntegrity, - "WEB_SERVER_404_PENALTY": WebServer404Penalty, - "WEBPAGE_UNAVAILABLE_PENALTY": WebpageUnavailablePenalty, - "GREEN_ADMIN_DATABASE_UNREACHABLE_PENALTY": GreenAdminDatabaseUnreachablePenalty, - "SHARED_REWARD": SharedReward, - "ACTION_PENALTY": ActionPenalty, - } - """List of reward class identifiers.""" - def __init__(self): """Initialise the reward function object.""" self.reward_components: List[Tuple[AbstractReward, float]] = [] @@ -457,7 +445,7 @@ class RewardFunction: @classmethod def from_config(cls, config: Dict) -> "RewardFunction": - """Create a reward function from a config dictionary. + """Create a reward function from a config dictionary and its related reward class. :param config: dict of options for the reward manager's constructor :type config: Dict @@ -468,8 +456,11 @@ class RewardFunction: for rew_component_cfg in config["reward_components"]: rew_type = rew_component_cfg["type"] + # XXX: If options key is missing add key then add type key. + if "options" not in rew_component_cfg: + rew_component_cfg["options"] = {} + rew_component_cfg["options"]["type"] = rew_type weight = rew_component_cfg.get("weight", 1.0) - rew_class = cls.rew_class_identifiers[rew_type] - rew_instance = rew_class.from_config(config=rew_component_cfg.get("options", {})) + rew_instance = AbstractReward.from_config(rew_component_cfg["options"]) new.register_component(component=rew_instance, weight=weight) return new diff --git a/src/primaite/game/game.py b/src/primaite/game/game.py index 7c5c93bc..51d0306c 100644 --- a/src/primaite/game/game.py +++ b/src/primaite/game/game.py @@ -629,7 +629,7 @@ class PrimaiteGame: for comp, weight in agent.reward_function.reward_components: if isinstance(comp, SharedReward): comp: SharedReward - graph[name].add(comp.agent_name) + graph[name].add(comp.config.agent_name) # while constructing the graph, we might as well set up the reward sharing itself. comp.callback = lambda agent_name: self.agents[agent_name].reward_function.current_reward diff --git a/tests/integration_tests/game_layer/test_rewards.py b/tests/integration_tests/game_layer/test_rewards.py index d4236d1b..6544c82d 100644 --- a/tests/integration_tests/game_layer/test_rewards.py +++ b/tests/integration_tests/game_layer/test_rewards.py @@ -25,7 +25,7 @@ def test_WebpageUnavailablePenalty(game_and_agent): agent: ControlledAgent schema = WebpageUnavailablePenalty.ConfigSchema(node_hostname="client_1", sticky=True) comp = WebpageUnavailablePenalty(config=schema) - + client_1 = game.simulation.network.get_node_by_hostname("client_1") browser: WebBrowser = client_1.software_manager.software.get("WebBrowser") browser.run() From 4c2ef6ea2a873d5d7bb965458db00bff21ef1dca Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Wed, 6 Nov 2024 14:52:22 +0000 Subject: [PATCH 30/58] #2913: Updated tests --- .../integration_tests/game_layer/test_rewards.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/tests/integration_tests/game_layer/test_rewards.py b/tests/integration_tests/game_layer/test_rewards.py index 6544c82d..742b2d35 100644 --- a/tests/integration_tests/game_layer/test_rewards.py +++ b/tests/integration_tests/game_layer/test_rewards.py @@ -18,7 +18,7 @@ from tests import TEST_ASSETS_ROOT from tests.conftest import ControlledAgent -def test_WebpageUnavailablePenalty(game_and_agent): +def test_WebpageUnavailablePenalty(game_and_agent: tuple[PrimaiteGame, ControlledAgent]): """Test that we get the right reward for failing to fetch a website.""" # set up the scenario, configure the web browser to the correct url game, agent = game_and_agent @@ -55,7 +55,7 @@ def test_WebpageUnavailablePenalty(game_and_agent): assert agent.reward_function.current_reward == -0.7 -def test_uc2_rewards(game_and_agent): +def test_uc2_rewards(game_and_agent: tuple[PrimaiteGame, ControlledAgent]): """Test that the reward component correctly applies a penalty when the selected client cannot reach the database.""" game, agent = game_and_agent agent: ControlledAgent @@ -142,8 +142,8 @@ def test_action_penalty_loads_from_config(): act_penalty_obj = comp[0] if act_penalty_obj is None: pytest.fail("Action penalty reward component was not added to the agent from config.") - assert act_penalty_obj.action_penalty == -0.75 - assert act_penalty_obj.do_nothing_penalty == 0.125 + assert act_penalty_obj.config.action_penalty == -0.75 + assert act_penalty_obj.config.do_nothing_penalty == 0.125 def test_action_penalty(): @@ -152,7 +152,7 @@ def test_action_penalty(): # Create an ActionPenalty Reward schema = ActionPenalty.ConfigSchema(action_penalty=-0.75, do_nothing_penalty=0.125) # Penalty = ActionPenalty(action_penalty=-0.75, do_nothing_penalty=0.125) - Penalty = ActionPenalty(schema) + Penalty = ActionPenalty(config=schema) # Assert that penalty is applied if action isn't DONOTHING reward_value = Penalty.calculate( @@ -183,11 +183,12 @@ def test_action_penalty(): assert reward_value == 0.125 -def test_action_penalty_e2e(game_and_agent): +def test_action_penalty_e2e(game_and_agent: tuple[PrimaiteGame, ControlledAgent]): """Test that we get the right reward for doing actions to fetch a website.""" game, agent = game_and_agent agent: ControlledAgent - comp = ActionPenalty(action_penalty=-0.75, do_nothing_penalty=0.125) + schema = ActionPenalty.ConfigSchema(action_penalty=-0.75, do_nothing_penalty=0.125) + comp = ActionPenalty(config=schema) agent.reward_function.register_component(comp, 1.0) From 9d6536fa6aee13858e39957d215061fecce5966f Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Wed, 6 Nov 2024 15:08:38 +0000 Subject: [PATCH 31/58] #2913: Pre-commit fix --- docs/source/how_to_guides/extensible_rewards.rst | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/source/how_to_guides/extensible_rewards.rst b/docs/source/how_to_guides/extensible_rewards.rst index 3505d66c..2551eee0 100644 --- a/docs/source/how_to_guides/extensible_rewards.rst +++ b/docs/source/how_to_guides/extensible_rewards.rst @@ -11,10 +11,13 @@ Changes to reward class structure. ================================== Reward classes are inherited from AbstractReward (a sub-class of Pydantic's BaseModel). -Within the reward class is a ConfigSchema class responsible for ensuring config file data is in the -correct format. The `.from_config()` method is generally unchanged. +Within the reward class there is a ConfigSchema class responsible for ensuring config file data is +in the correct format. The `.from_config()` method is generally unchanged but should initialise the +attributes edfined in the ConfigSchema. +Each class requires an identifier string which is used by the ConfigSchema class to verify that it +hasn't previously been added to the registry. -Inheriting from `BaseModel` removes the need for an `__init__` method bu means that object +Inheriting from `BaseModel` removes the need for an `__init__` method but means that object attributes need to be passed by keyword. .. code:: Python @@ -69,4 +72,4 @@ Changes to YAML file. .. code:: YAML There's no longer a need to provide a `dns_server` as an option in the simulation section - of the config file. \ No newline at end of file + of the config file. From e0b885cc79497bf21a010d3c7313d0fbaba0728c Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Thu, 7 Nov 2024 13:08:44 +0000 Subject: [PATCH 32/58] #2913: Changes to update test_sticky_rewards.py --- src/primaite/game/agent/rewards.py | 4 +-- .../_game/_agent/test_sticky_rewards.py | 33 +++++++++++++++---- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/src/primaite/game/agent/rewards.py b/src/primaite/game/agent/rewards.py index 029597a0..05bca033 100644 --- a/src/primaite/game/agent/rewards.py +++ b/src/primaite/game/agent/rewards.py @@ -211,7 +211,7 @@ class WebServer404Penalty(AbstractReward, identifier="WEB_SERVER_404_PENALTY"): return 1.0 if status == 200 else -1.0 if status == 404 else 0.0 self.reward = sum(map(status2rew, codes)) / len(codes) # convert form HTTP codes to rewards and average - elif not self.sticky: # there are no codes, but reward is not sticky, set reward to 0 + elif not self.config.sticky: # there are no codes, but reward is not sticky, set reward to 0 self.reward = 0.0 else: # skip calculating if sticky and no new codes. instead, reuse last step's value pass @@ -278,7 +278,7 @@ class WebpageUnavailablePenalty(AbstractReward, identifier="WEBPAGE_UNAVAILABLE_ elif web_browser_state is NOT_PRESENT_IN_STATE or not web_browser_state["history"]: _LOGGER.debug( "Web browser reward could not be calculated because the web browser history on node", - f"{self.node_hostname} was not reported in the simulation state. Returning 0.0", + f"{self.config.node_hostname} was not reported in the simulation state. Returning 0.0", ) self.reward = 0.0 else: diff --git a/tests/unit_tests/_primaite/_game/_agent/test_sticky_rewards.py b/tests/unit_tests/_primaite/_game/_agent/test_sticky_rewards.py index 2ad1a322..c758291f 100644 --- a/tests/unit_tests/_primaite/_game/_agent/test_sticky_rewards.py +++ b/tests/unit_tests/_primaite/_game/_agent/test_sticky_rewards.py @@ -11,7 +11,12 @@ from primaite.interface.request import RequestResponse class TestWebServer404PenaltySticky: def test_non_sticky(self): - reward = WebServer404Penalty(node_hostname="computer", service_name="WebService", sticky=False) + schema = WebServer404Penalty.ConfigSchema( + node_hostname="computer", + service_name="WebService", + sticky=False, + ) + reward = WebServer404Penalty(config=schema) # no response codes yet, reward is 0 codes = [] @@ -38,7 +43,12 @@ class TestWebServer404PenaltySticky: assert reward.calculate(state, last_action_response) == -1.0 def test_sticky(self): - reward = WebServer404Penalty(node_hostname="computer", service_name="WebService", sticky=True) + schema = WebServer404Penalty.ConfigSchema( + node_hostname="computer", + service_name="WebService", + sticky=True, + ) + reward = WebServer404Penalty(config=schema) # no response codes yet, reward is 0 codes = [] @@ -67,7 +77,8 @@ class TestWebServer404PenaltySticky: class TestWebpageUnavailabilitySticky: def test_non_sticky(self): - reward = WebpageUnavailablePenalty(node_hostname="computer", sticky=False) + schema = WebpageUnavailablePenalty.ConfigSchema(node_hostname="computer", sticky=False) + reward = WebpageUnavailablePenalty(config=schema) # no response codes yet, reward is 0 action, params, request = "DO_NOTHING", {}, ["DONOTHING"] @@ -127,7 +138,8 @@ class TestWebpageUnavailabilitySticky: assert reward.calculate(state, last_action_response) == -1.0 def test_sticky(self): - reward = WebpageUnavailablePenalty(node_hostname="computer", sticky=True) + schema = WebpageUnavailablePenalty.ConfigSchema(node_hostname="computer", sticky=True) + reward = WebpageUnavailablePenalty(config=schema) # no response codes yet, reward is 0 action, params, request = "DO_NOTHING", {}, ["DONOTHING"] @@ -188,7 +200,11 @@ class TestWebpageUnavailabilitySticky: class TestGreenAdminDatabaseUnreachableSticky: def test_non_sticky(self): - reward = GreenAdminDatabaseUnreachablePenalty(node_hostname="computer", sticky=False) + schema = GreenAdminDatabaseUnreachablePenalty.ConfigSchema( + node_hostname="computer", + sticky=False, + ) + reward = GreenAdminDatabaseUnreachablePenalty(config=schema) # no response codes yet, reward is 0 action, params, request = "DO_NOTHING", {}, ["DONOTHING"] @@ -214,7 +230,6 @@ class TestGreenAdminDatabaseUnreachableSticky: # agent did nothing, because reward is not sticky, it goes back to 0 action, params, request = "DO_NOTHING", {}, ["DONOTHING"] response = RequestResponse(status="success", data={}) - browser_history = [] state = {"network": {"nodes": {"computer": {"applications": {"DatabaseClient": {}}}}}} last_action_response = AgentHistoryItem( timestep=0, action=action, parameters=params, request=request, response=response @@ -244,7 +259,11 @@ class TestGreenAdminDatabaseUnreachableSticky: assert reward.calculate(state, last_action_response) == -1.0 def test_sticky(self): - reward = GreenAdminDatabaseUnreachablePenalty(node_hostname="computer", sticky=True) + schema = GreenAdminDatabaseUnreachablePenalty.ConfigSchema( + node_hostname="computer", + sticky=True, + ) + reward = GreenAdminDatabaseUnreachablePenalty(config=schema) # no response codes yet, reward is 0 action, params, request = "DO_NOTHING", {}, ["DONOTHING"] From 02d29f7fb9bb93ebe32c13503ee3e56dfe545369 Mon Sep 17 00:00:00 2001 From: Nick Todd Date: Thu, 7 Nov 2024 16:35:39 +0000 Subject: [PATCH 33/58] #2913: Updates to How-To guide --- .../how_to_guides/extensible_rewards.rst | 60 +++++++------------ 1 file changed, 21 insertions(+), 39 deletions(-) diff --git a/docs/source/how_to_guides/extensible_rewards.rst b/docs/source/how_to_guides/extensible_rewards.rst index 2551eee0..4dd24110 100644 --- a/docs/source/how_to_guides/extensible_rewards.rst +++ b/docs/source/how_to_guides/extensible_rewards.rst @@ -6,65 +6,47 @@ Extensible Rewards ****************** +Extensible Rewards differ from the previous reward mechanism used in PrimAITE v3.x as new reward +types can be added without requiring a change to the RewardFunction class in rewards.py (PrimAITE +core repository). Changes to reward class structure. ================================== Reward classes are inherited from AbstractReward (a sub-class of Pydantic's BaseModel). -Within the reward class there is a ConfigSchema class responsible for ensuring config file data is -in the correct format. The `.from_config()` method is generally unchanged but should initialise the -attributes edfined in the ConfigSchema. +Within the reward class there is a ConfigSchema class responsible for ensuring the config file data +is in the correct format. This also means there is little (if no) requirement for and `__init__` +method. The `.from_config` method is no longer required as it's inherited from `AbstractReward`. Each class requires an identifier string which is used by the ConfigSchema class to verify that it hasn't previously been added to the registry. Inheriting from `BaseModel` removes the need for an `__init__` method but means that object attributes need to be passed by keyword. -.. code:: Python +To add a new reward class follow the example below. Note that the type attribute in the +`ConfigSchema` class should match the type used in the config file to define the reward. -class AbstractReward(BaseModel): - """Base class for reward function components.""" +.. code-block:: Python - class ConfigSchema(BaseModel, ABC): - """Config schema for AbstractReward.""" +class DatabaseFileIntegrity(AbstractReward, identifier="DATABASE_FILE_INTEGRITY"): + """Reward function component which rewards the agent for maintaining the integrity of a database file.""" - type: str + config: "DatabaseFileIntegrity.ConfigSchema" + location_in_state: List[str] = [""] + reward: float = 0.0 - _registry: ClassVar[Dict[str, Type["AbstractReward"]]] = {} + class ConfigSchema(AbstractReward.ConfigSchema): + """ConfigSchema for DatabaseFileIntegrity.""" - def __init_subclass__(cls, identifier: str, **kwargs: Any) -> None: - super().__init_subclass__(**kwargs) - if identifier in cls._registry: - raise ValueError(f"Duplicate node adder {identifier}") - cls._registry[identifier] = cls + type: str = "DATABASE_FILE_INTEGRITY" + node_hostname: str + folder_name: str + file_name: str - @classmethod - def from_config(cls, config: Dict) -> "AbstractReward": - """Create a reward function component from a config dictionary. - - :param config: dict of options for the reward component's constructor - :type config: dict - :return: The reward component. - :rtype: AbstractReward - """ - if config["type"] not in cls._registry: - raise ValueError(f"Invalid reward type {config['type']}") - adder_class = cls._registry[config["type"]] - adder_class.add_nodes_to_net(config=adder_class.ConfigSchema(**config)) - return cls - - @abstractmethod def calculate(self, state: Dict, last_action_response: "AgentHistoryItem") -> float: """Calculate the reward for the current state. + pass - :param state: Current simulation state - :type state: Dict - :param last_action_response: Current agent history state - :type last_action_response: AgentHistoryItem state - :return: Reward value - :rtype: float - """ - return 0.0 Changes to YAML file. From d757bd01f0f634965e5999582412801edb177f37 Mon Sep 17 00:00:00 2001 From: Charlie Crane Date: Tue, 12 Nov 2024 14:49:44 +0000 Subject: [PATCH 34/58] #2912 - Updated to include __all__ and some test fixes. New extensible actions documentation page --- .../how_to_guides/extensible_actions.rst | 66 ++++++++ src/primaite/game/agent/actions.py | 26 +++- src/primaite/game/agent/actions/__init__.py | 2 +- src/primaite/game/agent/actions/abstract.py | 20 +-- src/primaite/game/agent/actions/acl.py | 145 +++++++----------- src/primaite/game/agent/actions/config.py | 24 +++ src/primaite/game/agent/actions/file.py | 2 + src/primaite/game/agent/actions/manager.py | 6 +- src/primaite/game/agent/actions/node.py | 10 +- src/primaite/game/agent/actions/session.py | 8 +- .../notebooks/Training-an-RLLib-Agent.ipynb | 4 +- .../configs/firewall_actions_network.yaml | 38 ++--- tests/conftest.py | 2 +- .../actions/test_configure_actions.py | 10 +- .../game_layer/test_actions.py | 36 ++--- 15 files changed, 239 insertions(+), 160 deletions(-) create mode 100644 docs/source/how_to_guides/extensible_actions.rst diff --git a/docs/source/how_to_guides/extensible_actions.rst b/docs/source/how_to_guides/extensible_actions.rst new file mode 100644 index 00000000..a6c12303 --- /dev/null +++ b/docs/source/how_to_guides/extensible_actions.rst @@ -0,0 +1,66 @@ +.. only:: comment + + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + +.. _about: + +Extensible Actions +****************** + +Actions defined within PrimAITE have been updated to allow for easier creation of new bespoke actions, without the need to make changes to the ActionManager class within the core PrimAITE repository. + + +Developing Actions for PrimAITE +=============================== + +When developing new actions for PrimAITE, it's important to ensure new actions inherit from the AbstractAction class. This is so that the `ActionManager` has visibility +of the new action through the `AbstractAction` registry attribute. This also removes the need for actions to contain an `__init__` method. + +New actions to be used within PrimAITE require: + +#. **ConfigSchema**: + + This should be a nested class that defines the required configuration items for the new action. + + .. code-block:: python + + class ExampleAction(AbstractAction, identifier="Example_action"): + + class ConfigSchema(AbstractAction.ConfigSchema): + target_application: str + + The ConfigSchema is used when the class is called to form the action. + + +#. **Unique Identifier**: + + New actions should have a Unique identifier when declared. This is used by the `ActionManager` when forming/processing action commands from agents. See the example code block in ConfigSchema for how this should be implemented. + +#. **form_request method**: + + New actions need a `form_request()` method, to convert the action into a ``Requestformat`` that can be ingested by PrimAITE's `RequestManager`. + The below is an example of how this is done, taken from the `NodeFolderCreateAction`. + + .. code-block:: python + + @classmethod + def form_request(cls, config: ConfigSchema) -> RequestFormat: + """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" + if config.node_name is None or config.folder_name is None: + return ["do_nothing"] + return [ + "network", + "node", + config.node_name, + "file_system", + cls.model_fields["verb"].default, + "folder", + config.folder_name, + ] + +There is no longer a need for a `from_config()` method to be defined within new actions, as this is handled within the base `AbstractAction` class. + +Changes to YAML file. +===================== + +Action identifiers now follow the snake_case naming style, instead of the MACRO_CASE that has been seen previously. Please review any custom YAML files for any issues seen. This should be backwards compatible. diff --git a/src/primaite/game/agent/actions.py b/src/primaite/game/agent/actions.py index 68e42fb1..885e0238 100644 --- a/src/primaite/game/agent/actions.py +++ b/src/primaite/game/agent/actions.py @@ -175,6 +175,10 @@ class NodeApplicationInstallAction(AbstractAction): class ConfigureDatabaseClientAction(AbstractAction): """Action which sets config parameters for a database client on a node.""" + model_config = ConfigDict(extra="forbid") + server_ip_address: Optional[str] = None + server_password: Optional[str] = None + class _Opts(BaseModel): """Schema for options that can be passed to this action.""" @@ -197,9 +201,10 @@ class ConfigureDatabaseClientAction(AbstractAction): class ConfigureRansomwareScriptAction(AbstractAction): """Action which sets config parameters for a ransomware script on a node.""" - class _Opts(BaseModel): + class _Opts(BaseModel, AbstractAction.ConfigSchema): """Schema for options that can be passed to this option.""" + node_name: str model_config = ConfigDict(extra="forbid") server_ip_address: Optional[str] = None server_password: Optional[str] = None @@ -208,13 +213,20 @@ class ConfigureRansomwareScriptAction(AbstractAction): def __init__(self, manager: "ActionManager", **kwargs) -> None: super().__init__(manager=manager) - def form_request(self, node_id: int, config: Dict) -> RequestFormat: + def form_request(self, config: _Opts) -> RequestFormat: """Return the action formatted as a request that can be ingested by the simulation.""" - node_name = self.manager.get_node_name_by_idx(node_id) - if node_name is None: + if config.node_name is None: return ["do_nothing"] ConfigureRansomwareScriptAction._Opts.model_validate(config) # check that options adhere to schema - return ["network", "node", node_name, "application", "RansomwareScript", "configure", config] + return [ + "network", + "node", + config.node_name, + "application", + "RansomwareScript", + "configure", + config.model_config, + ] class ConfigureDoSBotAction(AbstractAction): @@ -285,8 +297,8 @@ class NodeFolderAbstractAction(AbstractAction): class NodeFolderScanAction(NodeFolderAbstractAction): """Action which scans a folder.""" - def __init__(self, manager: "ActionManager", num_nodes: int, num_folders: int, **kwargs) -> None: - super().__init__(manager, num_nodes=num_nodes, num_folders=num_folders, **kwargs) + def __init__(self, manager: "ActionManager", node_name: str, folder_name, **kwargs) -> None: + super().__init__(manager, node_name=node_name, folder_name=folder_name, **kwargs) self.verb: str = "scan" diff --git a/src/primaite/game/agent/actions/__init__.py b/src/primaite/game/agent/actions/__init__.py index 3540b128..016a09ba 100644 --- a/src/primaite/game/agent/actions/__init__.py +++ b/src/primaite/game/agent/actions/__init__.py @@ -17,8 +17,8 @@ from primaite.game.agent.actions import ( from primaite.game.agent.actions.manager import ActionManager __all__ = ( - "acl", "abstract", + "acl", "application", "config", "file", diff --git a/src/primaite/game/agent/actions/abstract.py b/src/primaite/game/agent/actions/abstract.py index b96b14c9..2ed168d9 100644 --- a/src/primaite/game/agent/actions/abstract.py +++ b/src/primaite/game/agent/actions/abstract.py @@ -8,17 +8,18 @@ from pydantic import BaseModel, ConfigDict from primaite.interface.request import RequestFormat +# notes: +# we actually don't need to hold any state in actions, so there's no need to define any __init__ logic. +# all the init methods in the old actions are just used for holding a verb and shape, which are not really used. +# the config schema should be used to the actual parameters for formatting the action itself. +# (therefore there's no need for creating action instances, just the action class contains logic for converting +# CAOS actions to requests for simulator. Similar to the network node adder, that class also doesn't need to be +# instantiated.) + class AbstractAction(BaseModel): """Base class for actions.""" - # notes: - # we actually don't need to hold any state in actions, so there's no need to define any __init__ logic. - # all the init methods in the old actions are just used for holding a verb and shape, which are not really used. - # the config schema should be used to the actual parameters for formatting the action itself. - # (therefore there's no need for creating action instances, just the action class contains logic for converting - # CAOS actions to requests for simulator. Similar to the network node adder, that class also doesn't need to be - # instantiated.) class ConfigSchema(BaseModel, ABC): """Base configuration schema for Actions.""" @@ -41,12 +42,7 @@ class AbstractAction(BaseModel): @classmethod def from_config(cls, config: Dict) -> "AbstractAction": """Create an action component from a config dictionary.""" - # set attributes for action based off config dict - # if config["type"] not in cls._registry: - # raise ValueError(f"Invalid action reward type {config['type']}") - for attribute, value in config.items(): if not hasattr(cls.ConfigSchema, attribute): setattr(cls.ConfigSchema, attribute, value) - return cls diff --git a/src/primaite/game/agent/actions/acl.py b/src/primaite/game/agent/actions/acl.py index 8f5a79da..72a0b262 100644 --- a/src/primaite/game/agent/actions/acl.py +++ b/src/primaite/game/agent/actions/acl.py @@ -17,27 +17,35 @@ __all__ = ( class ACLAbstractAction(AbstractAction, identifier="acl_abstract_action"): """Base class for ACL actions.""" - class ConfigSchema(AbstractAction.ConfigSchema): """Configuration Schema base for ACL abstract actions.""" - - class RouterACLAddRuleAction(AbstractAction, identifier="router_acl_add_rule"): """Action which adds a rule to a router's ACL.""" + target_router: str + position: int + permission: Literal[1, 2] + src_ip: str + source_wildcard_id: int + source_port: str + dst_ip: str + dst_wildcard: int + dst_port: int + protocol_name: str + class ConfigSchema(AbstractAction.ConfigSchema): """Configuration Schema for RouterACLAddRuleAction.""" target_router: str position: int permission: Literal[1, 2] - source_ip_id: int - source_wildcard_id: int - source_port_id: int + src_ip: str + src_wildcard: int + source_port: str dst_ip: str - dst_wildcard_id: int + dst_wildcard: int dst_port: int protocol_name: str @@ -50,95 +58,54 @@ class RouterACLAddRuleAction(AbstractAction, identifier="router_acl_add_rule"): """At what position to add the rule, must be specified.""" permission: Literal[1, 2] """Whether to allow or deny traffic, must be specified. 1 = PERMIT, 2 = DENY.""" - source_ip_id: int = Field(default=1, ge=1) + src_ip: str """Rule source IP address. By default, all ip addresses.""" source_wildcard_id: int = Field(default=0, ge=0) """Rule source IP wildcard. By default, use the wildcard at index 0 from action manager.""" - source_port_id: int = Field(default=1, ge=1) + source_port: int = Field(default=1, ge=1) """Rule source port. By default, all source ports.""" dst_ip_id: int = Field(default=1, ge=1) """Rule destination IP address. By default, all ip addresses.""" - dst_wildcard_id: int = Field(default=0, ge=0) + dst_wildcard: int = Field(default=0, ge=0) """Rule destination IP wildcard. By default, use the wildcard at index 0 from action manager.""" dst_port_id: int = Field(default=1, ge=1) """Rule destination port. By default, all destination ports.""" protocol_name: str = "ALL" """Rule protocol. By default, all protocols.""" - @field_validator( - "source_ip_id", - "source_port_id", - "source_wildcard_id", - "dest_ip_id", - "dest_port_id", - "dest_wildcard_id", - "protocol_name", - mode="before", - ) - @classmethod - def not_none(cls, v: str, info: ValidationInfo) -> int: - """If None is passed, use the default value instead.""" - if v is None: - return cls.model_fields[info.field_name].default - return v + # @field_validator( + # "source_ip_id", + # "source_port_id", + # "source_wildcard_id", + # "dest_ip_id", + # "dest_port_id", + # "dest_wildcard_id", + # "protocol_name", + # mode="before", + # ) + # @classmethod + # def not_none(cls, v: str, info: ValidationInfo) -> int: + # """If None is passed, use the default value instead.""" + # if v is None: + # return cls.model_fields[info.field_name].default + # return v @classmethod def form_request(cls, config: ConfigSchema) -> List[str]: """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" # Validate incoming data. - parsed_options = RouterACLAddRuleAction.ACLRuleOptions( - target_router=config.target_router, - position=config.position, - permission=config.permission, - source_ip_id=config.source_ip_id, - source_wildcard_id=config.source_wildcard_id, - dest_ip_id=config.dst_ip, - dest_wildcard_id=config.dest_wildcard_id, - source_port_id=config.source_port_id, - dest_port_id=config.dst_port_id, - protocol=config.protocol_name, - ) - if parsed_options.permission == 1: - permission_str = "PERMIT" - elif parsed_options.permission == 2: - permission_str = "DENY" - # else: - # _LOGGER.warning(f"{self.__class__} received permission {permission}, expected 0 or 1.") - - if parsed_options.protocol_name == "ALL": - protocol = "ALL" - else: - protocol = parsed_options.protocol_name - # subtract 2 to account for UNUSED=0 and ALL=1. - - if parsed_options.source_ip_id == 1: - src_ip = "ALL" - else: - src_ip = cls.manager.get_ip_address_by_idx(parsed_options.source_ip_id - 2) - # subtract 2 to account for UNUSED=0, and ALL=1 - - src_wildcard = cls.manager.get_wildcard_by_idx(parsed_options.source_wildcard_id) - - if parsed_options.source_port_id == 1: - src_port = "ALL" - else: - src_port = cls.manager.get_port_by_idx(parsed_options.source_port_id - 2) - # subtract 2 to account for UNUSED=0, and ALL=1 - - if parsed_options.dest_ip_id == 1: - dst_ip = "ALL" - else: - dst_ip = cls.manager.get_ip_address_by_idx(parsed_options.dest_ip_id - 2) - # subtract 2 to account for UNUSED=0, and ALL=1 - dst_ip=config.dst_ip - - dst_wildcard = config.dest_wildcard_id - - if parsed_options.dest_port_id == 1: - dst_port = "ALL" - else: - dst_port = cls.manager.get_port_by_idx(parsed_options.dest_port_id - 2) - # subtract 2 to account for UNUSED=0, and ALL=1 + # parsed_options = RouterACLAddRuleAction.ACLRuleOptions( + # target_router=config.target_router, + # position=config.position, + # permission=config.permission, + # src_ip=config.src_ip, + # source_wildcard_id=config.source_wildcard_id, + # dst_ip_id=config.dst_ip, + # dst_wildcard=config.dst_wildcard, + # source_port_id=config.source_port_id, + # dest_port=config.dst_port, + # protocol=config.protocol_name, + # ) return [ "network", @@ -146,11 +113,11 @@ class RouterACLAddRuleAction(AbstractAction, identifier="router_acl_add_rule"): config.target_router, "acl", "add_rule", - config.permission_str, - protocol, - str(src_ip), + config.permission, + config.protocol_name, + config.src_ip, config.src_wildcard, - config.src_port, + config.source_port, str(config.dst_ip), config.dst_wildcard, config.dst_port, @@ -193,7 +160,6 @@ class FirewallACLAddRuleAction(ACLAbstractAction, identifier="firewall_acl_add_r num_permissions: int = 3 permission: str - def __init__( self, manager: "ActionManager", @@ -228,10 +194,8 @@ class FirewallACLAddRuleAction(ACLAbstractAction, identifier="firewall_acl_add_r "protocol_id": num_protocols, } - - @classmethod - def form_request(cls, config:ConfigSchema) -> List[str]: + def form_request(cls, config: ConfigSchema) -> List[str]: """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" if config.permission == 0: permission_str = "UNUSED" @@ -260,6 +224,7 @@ class FirewallACLAddRuleAction(ACLAbstractAction, identifier="firewall_acl_add_r else: # src_ip = self.manager.get_ip_address_by_idx(source_ip_id - 2) # subtract 2 to account for UNUSED=0, and ALL=1 + pass if config.source_port_id == 0: return ["do_nothing"] # invalid formulation @@ -286,8 +251,8 @@ class FirewallACLAddRuleAction(ACLAbstractAction, identifier="firewall_acl_add_r else: # dst_port = self.manager.get_port_by_idx(dest_port_id - 2) # subtract 2 to account for UNUSED=0, and ALL=1 - # src_wildcard = self.manager.get_wildcard_by_idx(source_wildcard_id) - # dst_wildcard = self.manager.get_wildcard_by_idx(dest_wildcard_id) + # src_wildcard = self.manager.get_wildcard_by_idx(source_wildcard_id) + # dst_wildcard = self.manager.get_wildcard_by_idx(dest_wildcard_id) pass return [ @@ -298,7 +263,7 @@ class FirewallACLAddRuleAction(ACLAbstractAction, identifier="firewall_acl_add_r config.firewall_port_direction, "acl", "add_rule", - permission_str, + config.permission, protocol, str(src_ip), config.src_wildcard, diff --git a/src/primaite/game/agent/actions/config.py b/src/primaite/game/agent/actions/config.py index beda8f27..a4247e21 100644 --- a/src/primaite/game/agent/actions/config.py +++ b/src/primaite/game/agent/actions/config.py @@ -15,6 +15,7 @@ __all__ = ( "TerminalC2ServerAction", "RansomwareLaunchC2ServerAction", "ExfiltrationC2ServerAction", + "ConfigureDatabaseClientAction", ) @@ -230,3 +231,26 @@ class ExfiltrationC2ServerAction(AbstractAction, identifier="c2_server_data_exfi } ExfiltrationC2ServerAction._Opts.model_validate(command_model) return ["network", "node", node_name, "application", "C2Server", "exfiltrate", command_model] + + +class ConfigureDatabaseClientAction(AbstractAction, identifier="configure_database_client"): + """Action which sets config parameters for a database client on a node.""" + + node_name: str + model_config: ConfigDict = ConfigDict(extra="forbid") + + class ConfigSchema(AbstractAction.ConfigSchema): + """Schema for options that can be passed to this action.""" + + node_name: str + model_config = ConfigDict(extra="forbid") + + def __init__(self, manager: "ActionManager", **kwargs) -> None: + super().__init__(manager=manager) + + def form_request(self, config: ConfigSchema) -> RequestFormat: + """Return the action formatted as a request that can be ingested by the simulation.""" + + if config.node_name is None: + return ["do_nothing"] + return ["network", "node", config.node_name, "application", "DatabaseClient", "configure", config.model_config] diff --git a/src/primaite/game/agent/actions/file.py b/src/primaite/game/agent/actions/file.py index 6d1f95b9..c5ba1602 100644 --- a/src/primaite/game/agent/actions/file.py +++ b/src/primaite/game/agent/actions/file.py @@ -11,6 +11,8 @@ __all__ = ( "NodeFileRestoreAction", "NodeFileCorruptAction", "NodeFileAccessAction", + "NodeFileCheckhashAction", + "NodeFileRepairAction", ) diff --git a/src/primaite/game/agent/actions/manager.py b/src/primaite/game/agent/actions/manager.py index 26814bf2..764b5b6e 100644 --- a/src/primaite/game/agent/actions/manager.py +++ b/src/primaite/game/agent/actions/manager.py @@ -24,6 +24,8 @@ from primaite.interface.request import RequestFormat # TODO: Make sure that actions are backwards compatible where the old YAML format is used. +__all__ = "DoNothingAction" + class DoNothingAction(AbstractAction, identifier="do_nothing"): """Do Nothing Action.""" @@ -69,7 +71,7 @@ class ActionManager: Action mapping that converts an integer to a specific action and parameter choice. For example : - {0: ("NODE_SERVICE_SCAN", {node_id:0, service_id:2})} + {0: ("node_service_scan", {node_name:"client_1", service_name:"WebBrowser"})} """ if act_map is None: # raise RuntimeError("Action map must be specified in the config file.") @@ -147,7 +149,7 @@ class ActionManager: Since the agent uses a discrete action space which acts as a flattened version of the component-based action space, action_map provides a mapping between an integer (chosen by the agent) and a meaningful action and values of parameters. For example action 0 can correspond to do nothing, action 1 can - correspond to "NODE_SERVICE_SCAN" with ``node_id=1`` and ``service_id=1``, action 2 can be " + correspond to "node_service_scan" with ``node_name="server"`` and ``service_name="WebBrowser"``, action 2 can be " 3. ``options`` ``options`` contains a dictionary of options which are passed to the ActionManager's __init__ method. These options are used to calculate the shape of the action space, and to provide additional information diff --git a/src/primaite/game/agent/actions/node.py b/src/primaite/game/agent/actions/node.py index 29833b15..ab4209e5 100644 --- a/src/primaite/game/agent/actions/node.py +++ b/src/primaite/game/agent/actions/node.py @@ -5,7 +5,15 @@ from typing import ClassVar, List, Optional, Union from primaite.game.agent.actions.manager import AbstractAction from primaite.interface.request import RequestFormat -__all__ = ("NodeOSScanAction", "NodeShutdownAction", "NodeStartupAction", "NodeResetAction") +__all__ = ( + "NodeOSScanAction", + "NodeShutdownAction", + "NodeStartupAction", + "NodeResetAction", + "NodeNMAPPingScanAction", + "NodeNMAPPortScanAction", + "NodeNetworkServiceReconAction", +) class NodeAbstractAction(AbstractAction, identifier="node_abstract"): diff --git a/src/primaite/game/agent/actions/session.py b/src/primaite/game/agent/actions/session.py index 76b97cbd..79ff0705 100644 --- a/src/primaite/game/agent/actions/session.py +++ b/src/primaite/game/agent/actions/session.py @@ -4,7 +4,11 @@ from abc import abstractmethod from primaite.game.agent.actions.manager import AbstractAction from primaite.interface.request import RequestFormat -__all__ = ("NodeSessionsRemoteLoginAction", "NodeSessionsRemoteLogoutAction") +__all__ = ( + "NodeSessionsRemoteLoginAction", + "NodeSessionsRemoteLogoutAction", + "NodeAccountChangePasswordAction", +) class NodeSessionAbstractAction(AbstractAction, identifier="node_session_abstract"): @@ -73,7 +77,7 @@ class NodeSessionsRemoteLogoutAction(NodeSessionAbstractAction, identifier="node return ["network", "node", config.node_name, "service", "Terminal", "remote_logoff", config.remote_ip] -class NodeAccountsChangePasswordAction(NodeSessionAbstractAction, identifier="node_accounts_change_password"): +class NodeAccountChangePasswordAction(NodeSessionAbstractAction, identifier="node_account_change_password"): """Action which changes the password for a user.""" username: str diff --git a/src/primaite/notebooks/Training-an-RLLib-Agent.ipynb b/src/primaite/notebooks/Training-an-RLLib-Agent.ipynb index dbe8871c..0fd212f2 100644 --- a/src/primaite/notebooks/Training-an-RLLib-Agent.ipynb +++ b/src/primaite/notebooks/Training-an-RLLib-Agent.ipynb @@ -95,7 +95,7 @@ ], "metadata": { "kernelspec": { - "display_name": "venv", + "display_name": ".venv", "language": "python", "name": "python3" }, @@ -109,7 +109,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.12" + "version": "3.10.11" } }, "nbformat": 4, diff --git a/tests/assets/configs/firewall_actions_network.yaml b/tests/assets/configs/firewall_actions_network.yaml index 18e27de7..88b09a29 100644 --- a/tests/assets/configs/firewall_actions_network.yaml +++ b/tests/assets/configs/firewall_actions_network.yaml @@ -97,16 +97,16 @@ agents: action_space: action_list: - type: do_nothing - - type: FIREWALL_ACL_ADDRULE firewall_acl_add_rule - - type: FIREWALL_ACL_REMOVERULE - - type: NETWORK_PORT_DISABLE - - type: NETWORK_PORT_ENABLE + - type: firewall_acl_add_rule + - type: firewall_acl_remove_rule + - type: network_port_disable + - type: network_port_enable action_map: 0: - action: DONOTHING + action: do_nothing options: {} 1: - action: FIREWALL_ACL_ADDRULE + action: firewall_acl_add_rule options: target_firewall_nodename: firewall firewall_port_name: internal @@ -121,14 +121,14 @@ agents: source_wildcard_id: 0 dest_wildcard_id: 0 2: - action: FIREWALL_ACL_REMOVERULE + action: firewall_acl_remove_rule options: target_firewall_nodename: firewall firewall_port_name: internal firewall_port_direction: inbound position: 1 3: - action: FIREWALL_ACL_ADDRULE + action: firewall_acl_add_rule options: target_firewall_nodename: firewall firewall_port_name: internal @@ -143,14 +143,14 @@ agents: source_wildcard_id: 0 dest_wildcard_id: 0 4: - action: FIREWALL_ACL_REMOVERULE + action: firewall_acl_remove_rule options: target_firewall_nodename: firewall firewall_port_name: internal firewall_port_direction: outbound position: 1 5: - action: FIREWALL_ACL_ADDRULE + action: firewall_acl_add_rule options: target_firewall_nodename: firewall firewall_port_name: dmz @@ -165,14 +165,14 @@ agents: source_wildcard_id: 0 dest_wildcard_id: 0 6: - action: FIREWALL_ACL_REMOVERULE + action: firewall_acl_remove_rule options: target_firewall_nodename: firewall firewall_port_name: dmz firewall_port_direction: inbound position: 1 7: - action: FIREWALL_ACL_ADDRULE + action: firewall_acl_add_rule options: target_firewall_nodename: firewall firewall_port_name: dmz @@ -187,14 +187,14 @@ agents: source_wildcard_id: 0 dest_wildcard_id: 0 8: - action: FIREWALL_ACL_REMOVERULE + action: firewall_acl_remove_rule options: target_firewall_nodename: firewall firewall_port_name: dmz firewall_port_direction: outbound position: 2 9: - action: FIREWALL_ACL_ADDRULE + action: firewall_acl_add_rule options: target_firewall_nodename: firewall firewall_port_name: external @@ -209,14 +209,14 @@ agents: source_wildcard_id: 0 dest_wildcard_id: 0 10: - action: FIREWALL_ACL_REMOVERULE + action: firewall_acl_remove_rule options: target_firewall_nodename: firewall firewall_port_name: external firewall_port_direction: inbound position: 10 11: - action: FIREWALL_ACL_ADDRULE + action: firewall_acl_add_rule options: target_firewall_nodename: firewall firewall_port_name: external @@ -231,19 +231,19 @@ agents: source_wildcard_id: 0 dest_wildcard_id: 0 12: - action: FIREWALL_ACL_REMOVERULE + action: firewall_acl_remove_rule options: target_firewall_nodename: firewall firewall_port_name: external firewall_port_direction: outbound position: 1 13: - action: NETWORK_PORT_DISABLE + action: network_port_disable options: target_nodename: firewall port_id: 3 14: - action: NETWORK_PORT_ENABLE + action: network_port_enable options: target_nodename: firewall port_id: 3 diff --git a/tests/conftest.py b/tests/conftest.py index 8bfc78a4..bd1b79ee 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -463,7 +463,7 @@ def game_and_agent(): {"type": "c2_server_ransomware_configure"}, {"type": "c2_server_terminal_command"}, {"type": "c2_server_data_exfiltrate"}, - {"type": "node_accounts_change_password"}, + {"type": "node_account_change_password"}, {"type": "node_session_remote_login"}, {"type": "node_session_remote_logoff"}, {"type": "node_send_remote_command"}, diff --git a/tests/integration_tests/game_layer/actions/test_configure_actions.py b/tests/integration_tests/game_layer/actions/test_configure_actions.py index 508bd5a4..7bf45fb4 100644 --- a/tests/integration_tests/game_layer/actions/test_configure_actions.py +++ b/tests/integration_tests/game_layer/actions/test_configure_actions.py @@ -4,7 +4,7 @@ from ipaddress import IPv4Address import pytest from pydantic import ValidationError -from primaite.game.agent.actions import ( +from primaite.game.agent.actions.config import ( ConfigureDatabaseClientAction, ConfigureDoSBotAction, ConfigureRansomwareScriptAction, @@ -35,10 +35,10 @@ class TestConfigureDatabaseAction: db_client: DatabaseClient = client_1.software_manager.software["DatabaseClient"] action = ( - "CONFIGURE_DATABASE_CLIENT", + "configure_database_client", { - "node_id": 0, - "config": { + "node_name": "client_1", + "model_config": { "server_ip_address": "192.168.1.99", "server_password": "admin123", }, @@ -53,7 +53,7 @@ class TestConfigureDatabaseAction: def test_configure_ip(self, game_and_agent): game, agent = game_and_agent agent: ControlledAgent - agent.action_manager.actions["CONFIGURE_DATABASE_CLIENT"] = ConfigureDatabaseClientAction(agent.action_manager) + agent.action_manager.actions["configure_database_client"] = ConfigureDatabaseClientAction(agent.action_manager) # make sure there is a database client on this node client_1 = game.simulation.network.get_node_by_hostname("client_1") diff --git a/tests/integration_tests/game_layer/test_actions.py b/tests/integration_tests/game_layer/test_actions.py index 21aa31de..baa4c725 100644 --- a/tests/integration_tests/game_layer/test_actions.py +++ b/tests/integration_tests/game_layer/test_actions.py @@ -124,15 +124,15 @@ def test_router_acl_addrule_integration(game_and_agent: Tuple[PrimaiteGame, Prox "router_acl_add_rule", { "target_router": "router", - "position": 4, # 4th rule - "permission": 2, # DENY - "source_ip_id": 3, # 10.0.1.2 (client_1) - "dest_ip_id": 6, # 10.0.2.3 (server_2) - "dest_port_id": 1, # ALL - "source_port_id": 1, # ALL - "protocol_name": "ALL", # ALL - "source_wildcard_id": 0, - "dest_wildcard_id": 0, + "position": 4, + "permission": "DENY", + "src_ip": "10.0.1.2", + "src_wildcard": 0, + "source_port": "ALL", + "dst_ip": "10.0.2.3", + "dst_wildcard": 0, + "dst_port": "ALL", + "protocol_name": "ALL", }, ) agent.store_action(action) @@ -148,18 +148,18 @@ def test_router_acl_addrule_integration(game_and_agent: Tuple[PrimaiteGame, Prox # 4: Add a rule to block server_1 from reaching server_2 on router (this should not affect comms as they are on same subnet) action = ( - "ROUTER_ACL_ADDRULE", + "router_acl_add_rule", { "target_router": "router", "position": 5, # 5th rule - "permission": 2, # DENY - "source_ip_id": 5, # 10.0.2.2 (server_1) - "dest_ip_id": 6, # 10.0.2.3 (server_2) - "dest_port_id": 1, # ALL - "source_port_id": 1, # ALL - "protocol_id": 1, # ALL - "source_wildcard_id": 0, - "dest_wildcard_id": 0, + "permission": "DENY", # DENY + "src_ip": "10.0.2.2", # 10.0.2.2 (server_1) + "src_wildcard": 0, + "source_port": "ALL", # ALL + "dst_ip": "10.0.2.3", # 10.0.2.3 (server_2) + "dst_wildcard": 0, + "dst_port": "ALL", # ALL + "protocol_name": "ALL", # ALL }, ) agent.store_action(action) From ed020f005fde98504749e0060241a512dcc83ce4 Mon Sep 17 00:00:00 2001 From: Charlie Crane Date: Wed, 13 Nov 2024 10:40:51 +0000 Subject: [PATCH 35/58] #2912 - Pre-commit updates ahead of first draft PR. --- .../how_to_guides/extensible_actions.rst | 2 +- src/primaite/game/agent/actions.py | 2 +- src/primaite/game/agent/actions/acl.py | 174 ++---------------- src/primaite/game/agent/actions/config.py | 1 - src/primaite/game/agent/actions/file.py | 2 +- src/primaite/game/agent/actions/manager.py | 3 +- src/primaite/game/agent/actions/node.py | 8 + src/primaite/game/agent/actions/session.py | 4 +- .../game_layer/test_actions.py | 1 + 9 files changed, 36 insertions(+), 161 deletions(-) diff --git a/docs/source/how_to_guides/extensible_actions.rst b/docs/source/how_to_guides/extensible_actions.rst index a6c12303..bd78c8e1 100644 --- a/docs/source/how_to_guides/extensible_actions.rst +++ b/docs/source/how_to_guides/extensible_actions.rst @@ -29,7 +29,7 @@ New actions to be used within PrimAITE require: class ConfigSchema(AbstractAction.ConfigSchema): target_application: str - The ConfigSchema is used when the class is called to form the action. + The ConfigSchema is used when the class is called to form the action, within the `form_request` method, detailed below. #. **Unique Identifier**: diff --git a/src/primaite/game/agent/actions.py b/src/primaite/game/agent/actions.py index 885e0238..02134650 100644 --- a/src/primaite/game/agent/actions.py +++ b/src/primaite/game/agent/actions.py @@ -297,7 +297,7 @@ class NodeFolderAbstractAction(AbstractAction): class NodeFolderScanAction(NodeFolderAbstractAction): """Action which scans a folder.""" - def __init__(self, manager: "ActionManager", node_name: str, folder_name, **kwargs) -> None: + def __init__(self, manager: "ActionManager", node_name: str, folder_name: str, **kwargs) -> None: super().__init__(manager, node_name=node_name, folder_name=folder_name, **kwargs) self.verb: str = "scan" diff --git a/src/primaite/game/agent/actions/acl.py b/src/primaite/game/agent/actions/acl.py index 72a0b262..d6d5f4b4 100644 --- a/src/primaite/game/agent/actions/acl.py +++ b/src/primaite/game/agent/actions/acl.py @@ -1,9 +1,7 @@ # © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK -from typing import Dict, List, Literal +from typing import List, Literal -from pydantic import BaseModel, Field, field_validator, ValidationInfo - -from primaite.game.agent.actions.manager import AbstractAction, ActionManager +from primaite.game.agent.actions.manager import AbstractAction from primaite.interface.request import RequestFormat __all__ = ( @@ -20,6 +18,9 @@ class ACLAbstractAction(AbstractAction, identifier="acl_abstract_action"): class ConfigSchema(AbstractAction.ConfigSchema): """Configuration Schema base for ACL abstract actions.""" + src_ip: str + protocol_name: str + class RouterACLAddRuleAction(AbstractAction, identifier="router_acl_add_rule"): """Action which adds a rule to a router's ACL.""" @@ -27,13 +28,11 @@ class RouterACLAddRuleAction(AbstractAction, identifier="router_acl_add_rule"): target_router: str position: int permission: Literal[1, 2] - src_ip: str source_wildcard_id: int source_port: str dst_ip: str dst_wildcard: int dst_port: int - protocol_name: str class ConfigSchema(AbstractAction.ConfigSchema): """Configuration Schema for RouterACLAddRuleAction.""" @@ -47,66 +46,10 @@ class RouterACLAddRuleAction(AbstractAction, identifier="router_acl_add_rule"): dst_ip: str dst_wildcard: int dst_port: int - protocol_name: str - - class ACLRuleOptions(BaseModel): - """Validator for ACL_ADD_RULE options.""" - - target_router: str - """On which router to add the rule, must be specified.""" - position: int - """At what position to add the rule, must be specified.""" - permission: Literal[1, 2] - """Whether to allow or deny traffic, must be specified. 1 = PERMIT, 2 = DENY.""" - src_ip: str - """Rule source IP address. By default, all ip addresses.""" - source_wildcard_id: int = Field(default=0, ge=0) - """Rule source IP wildcard. By default, use the wildcard at index 0 from action manager.""" - source_port: int = Field(default=1, ge=1) - """Rule source port. By default, all source ports.""" - dst_ip_id: int = Field(default=1, ge=1) - """Rule destination IP address. By default, all ip addresses.""" - dst_wildcard: int = Field(default=0, ge=0) - """Rule destination IP wildcard. By default, use the wildcard at index 0 from action manager.""" - dst_port_id: int = Field(default=1, ge=1) - """Rule destination port. By default, all destination ports.""" - protocol_name: str = "ALL" - """Rule protocol. By default, all protocols.""" - - # @field_validator( - # "source_ip_id", - # "source_port_id", - # "source_wildcard_id", - # "dest_ip_id", - # "dest_port_id", - # "dest_wildcard_id", - # "protocol_name", - # mode="before", - # ) - # @classmethod - # def not_none(cls, v: str, info: ValidationInfo) -> int: - # """If None is passed, use the default value instead.""" - # if v is None: - # return cls.model_fields[info.field_name].default - # return v @classmethod def form_request(cls, config: ConfigSchema) -> List[str]: """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - # Validate incoming data. - # parsed_options = RouterACLAddRuleAction.ACLRuleOptions( - # target_router=config.target_router, - # position=config.position, - # permission=config.permission, - # src_ip=config.src_ip, - # source_wildcard_id=config.source_wildcard_id, - # dst_ip_id=config.dst_ip, - # dst_wildcard=config.dst_wildcard, - # source_port_id=config.source_port_id, - # dest_port=config.dst_port, - # protocol=config.protocol_name, - # ) - return [ "network", "node", @@ -118,7 +61,7 @@ class RouterACLAddRuleAction(AbstractAction, identifier="router_acl_add_rule"): config.src_ip, config.src_wildcard, config.source_port, - str(config.dst_ip), + config.dst_ip, config.dst_wildcard, config.dst_port, config.position, @@ -160,63 +103,11 @@ class FirewallACLAddRuleAction(ACLAbstractAction, identifier="firewall_acl_add_r num_permissions: int = 3 permission: str - def __init__( - self, - manager: "ActionManager", - max_acl_rules: int, - num_ips: int, - num_ports: int, - num_protocols: int, - **kwargs, - ) -> None: - """Init method for FirewallACLAddRuleAction. - - :param manager: Reference to the ActionManager which created this action. - :type manager: ActionManager - :param max_acl_rules: Maximum number of ACL rules that can be added to the router. - :type max_acl_rules: int - :param num_ips: Number of IP addresses in the simulation. - :type num_ips: int - :param num_ports: Number of ports in the simulation. - :type num_ports: int - :param num_protocols: Number of protocols in the simulation. - :type num_protocols: int - """ - super().__init__(manager=manager) - num_permissions = 3 - self.shape: Dict[str, int] = { - "position": max_acl_rules, - "permission": num_permissions, - "source_ip_id": num_ips, - "dest_ip_id": num_ips, - "source_port_id": num_ports, - "dest_port_id": num_ports, - "protocol_id": num_protocols, - } - @classmethod def form_request(cls, config: ConfigSchema) -> List[str]: """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - if config.permission == 0: - permission_str = "UNUSED" - return ["do_nothing"] # NOT SUPPORTED, JUST DO NOTHING IF WE COME ACROSS THIS - elif config.permission == 1: - permission_str = "PERMIT" - elif config.permission == 2: - permission_str = "DENY" - # else: - # _LOGGER.warning(f"{self.__class__} received permission {permission}, expected 0 or 1.") - if config.protocol_id == 0: return ["do_nothing"] # NOT SUPPORTED, JUST DO NOTHING IF WE COME ACROSS THIS - - if config.protocol_id == 1: - protocol = "ALL" - else: - # protocol = self.manager.get_internet_protocol_by_idx(protocol_id - 2) - # subtract 2 to account for UNUSED=0 and ALL=1. - pass - if config.source_ip_id == 0: return ["do_nothing"] # invalid formulation elif config.source_ip_id == 1: @@ -235,26 +126,6 @@ class FirewallACLAddRuleAction(ACLAbstractAction, identifier="firewall_acl_add_r # subtract 2 to account for UNUSED=0, and ALL=1 pass - if config.dest_ip_id == 0: - return ["do_nothing"] # invalid formulation - elif config.dest_ip_id == 1: - dst_ip = "ALL" - else: - # dst_ip = self.manager.get_ip_address_by_idx(dest_ip_id - 2) - # subtract 2 to account for UNUSED=0, and ALL=1 - pass - - if config.dest_port_id == 0: - return ["do_nothing"] # invalid formulation - elif config.dest_port_id == 1: - dst_port = "ALL" - else: - # dst_port = self.manager.get_port_by_idx(dest_port_id - 2) - # subtract 2 to account for UNUSED=0, and ALL=1 - # src_wildcard = self.manager.get_wildcard_by_idx(source_wildcard_id) - # dst_wildcard = self.manager.get_wildcard_by_idx(dest_wildcard_id) - pass - return [ "network", "node", @@ -264,13 +135,13 @@ class FirewallACLAddRuleAction(ACLAbstractAction, identifier="firewall_acl_add_r "acl", "add_rule", config.permission, - protocol, + config.protocol_name, str(src_ip), config.src_wildcard, src_port, - str(dst_ip), + config.dst_ip, config.dst_wildcard, - dst_port, + config.dst_port, config.position, ] @@ -278,29 +149,24 @@ class FirewallACLAddRuleAction(ACLAbstractAction, identifier="firewall_acl_add_r class FirewallACLRemoveRuleAction(AbstractAction, identifier="firewall_acl_remove_rule"): """Action which removes a rule from a firewall port's ACL.""" - def __init__(self, manager: "ActionManager", max_acl_rules: int, **kwargs) -> None: - """Init method for RouterACLRemoveRuleAction. + class ConfigSchema(AbstractAction.ConfigSchema): + """Configuration schema for FirewallACLRemoveRuleAction.""" - :param manager: Reference to the ActionManager which created this action. - :type manager: ActionManager - :param max_acl_rules: Maximum number of ACL rules that can be added to the router. - :type max_acl_rules: int - """ - super().__init__(manager=manager) - self.shape: Dict[str, int] = {"position": max_acl_rules} + target_firewall_nodename: str + firewall_port_name: str + firewall_port_direction: str + position: int @classmethod - def form_request( - cls, target_firewall_nodename: str, firewall_port_name: str, firewall_port_direction: str, position: int - ) -> List[str]: + def form_request(cls, config: ConfigSchema) -> List[str]: """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" return [ "network", "node", - target_firewall_nodename, - firewall_port_name, - firewall_port_direction, + config.target_firewall_nodename, + config.firewall_port_name, + config.firewall_port_direction, "acl", "remove_rule", - position, + config.position, ] diff --git a/src/primaite/game/agent/actions/config.py b/src/primaite/game/agent/actions/config.py index a4247e21..d7b436d7 100644 --- a/src/primaite/game/agent/actions/config.py +++ b/src/primaite/game/agent/actions/config.py @@ -250,7 +250,6 @@ class ConfigureDatabaseClientAction(AbstractAction, identifier="configure_databa def form_request(self, config: ConfigSchema) -> RequestFormat: """Return the action formatted as a request that can be ingested by the simulation.""" - if config.node_name is None: return ["do_nothing"] return ["network", "node", config.node_name, "application", "DatabaseClient", "configure", config.model_config] diff --git a/src/primaite/game/agent/actions/file.py b/src/primaite/game/agent/actions/file.py index c5ba1602..5d12b27a 100644 --- a/src/primaite/game/agent/actions/file.py +++ b/src/primaite/game/agent/actions/file.py @@ -178,7 +178,7 @@ class NodeFileCheckhashAction(NodeFileAbstractAction, identifier="node_file_chec class NodeFileRepairAction(NodeFileAbstractAction, identifier="node_file_repair"): - """Action which repairs a file""" + """Action which repairs a file.""" verb: str = "repair" diff --git a/src/primaite/game/agent/actions/manager.py b/src/primaite/game/agent/actions/manager.py index 764b5b6e..6c8353b0 100644 --- a/src/primaite/game/agent/actions/manager.py +++ b/src/primaite/game/agent/actions/manager.py @@ -149,7 +149,8 @@ class ActionManager: Since the agent uses a discrete action space which acts as a flattened version of the component-based action space, action_map provides a mapping between an integer (chosen by the agent) and a meaningful action and values of parameters. For example action 0 can correspond to do nothing, action 1 can - correspond to "node_service_scan" with ``node_name="server"`` and ``service_name="WebBrowser"``, action 2 can be " + correspond to "node_service_scan" with ``node_name="server"`` and + ``service_name="WebBrowser"``, action 2 can be " 3. ``options`` ``options`` contains a dictionary of options which are passed to the ActionManager's __init__ method. These options are used to calculate the shape of the action space, and to provide additional information diff --git a/src/primaite/game/agent/actions/node.py b/src/primaite/game/agent/actions/node.py index ab4209e5..a69a8a5f 100644 --- a/src/primaite/game/agent/actions/node.py +++ b/src/primaite/game/agent/actions/node.py @@ -89,7 +89,11 @@ class NodeNMAPAbstractAction(AbstractAction, identifier="node_nmap_abstract_acti class NodeNMAPPingScanAction(NodeNMAPAbstractAction, identifier="node_nmap_ping_scan"): + """Action which performs an NMAP ping scan.""" + class ConfigSchema(NodeNMAPAbstractAction.ConfigSchema): + """Configuration schema for NodeNMAPPingScanAction.""" + pass @classmethod @@ -110,6 +114,8 @@ class NodeNMAPPortScanAction(NodeNMAPAbstractAction, identifier="node_nmap_port_ """Action which performs an NMAP port scan.""" class ConfigSchema(NodeNMAPAbstractAction.ConfigSchema): + """Configuration Schema for NodeNMAPPortScanAction.""" + source_node: str target_protocol: Optional[Union[str, List[str]]] = (None,) target_port: Optional[Union[str, List[str]]] = (None,) @@ -141,6 +147,8 @@ class NodeNetworkServiceReconAction(NodeNMAPAbstractAction, identifier="node_net """Action which performs an NMAP network service recon (ping scan followed by port scan).""" class ConfigSchema(AbstractAction.ConfigSchema): + """Configuration schema for NodeNetworkServiceReconAction.""" + target_protocol: Optional[Union[str, List[str]]] = (None,) target_port: Optional[Union[str, List[str]]] = (None,) show: Optional[bool] = (False,) diff --git a/src/primaite/game/agent/actions/session.py b/src/primaite/game/agent/actions/session.py index 79ff0705..dcae8b47 100644 --- a/src/primaite/game/agent/actions/session.py +++ b/src/primaite/game/agent/actions/session.py @@ -67,14 +67,14 @@ class NodeSessionsRemoteLogoutAction(NodeSessionAbstractAction, identifier="node class ConfigSchema(NodeSessionAbstractAction.ConfigSchema): """Configuration schema for NodeSessionsRemoteLogoutAction.""" - pass + verb: str = "remote_logoff" @classmethod def form_request(cls, config: ConfigSchema) -> RequestFormat: """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" if config.node_name is None or config.remote_ip is None: return ["do_nothing"] - return ["network", "node", config.node_name, "service", "Terminal", "remote_logoff", config.remote_ip] + return ["network", "node", config.node_name, "service", "Terminal", config.verb, config.remote_ip] class NodeAccountChangePasswordAction(NodeSessionAbstractAction, identifier="node_account_change_password"): diff --git a/tests/integration_tests/game_layer/test_actions.py b/tests/integration_tests/game_layer/test_actions.py index baa4c725..f380ba7d 100644 --- a/tests/integration_tests/game_layer/test_actions.py +++ b/tests/integration_tests/game_layer/test_actions.py @@ -166,6 +166,7 @@ def test_router_acl_addrule_integration(game_and_agent: Tuple[PrimaiteGame, Prox game.step() # 5: Check that the ACL now has 6 rules, but that server_1 can still ping server_2 + print(router.acl.show()) assert router.acl.num_rules == 6 assert server_1.ping("10.0.2.3") # Can ping server_2 From 95fbe451371dbc6a96bf5c7dc9b9a2a1ccdba4a9 Mon Sep 17 00:00:00 2001 From: Charlie Crane Date: Wed, 13 Nov 2024 15:32:48 +0000 Subject: [PATCH 36/58] #2912 - Updates so that all tests within test_actions.py pass --- src/primaite/game/agent/actions.py | 5 -- src/primaite/game/agent/actions/abstract.py | 3 +- src/primaite/game/agent/actions/acl.py | 37 +++++---- src/primaite/game/agent/actions/config.py | 2 +- .../configs/firewall_actions_network.yaml | 76 +++++++++---------- .../game_layer/test_actions.py | 9 ++- 6 files changed, 63 insertions(+), 69 deletions(-) diff --git a/src/primaite/game/agent/actions.py b/src/primaite/game/agent/actions.py index 02134650..64cbe0cf 100644 --- a/src/primaite/game/agent/actions.py +++ b/src/primaite/game/agent/actions.py @@ -175,10 +175,6 @@ class NodeApplicationInstallAction(AbstractAction): class ConfigureDatabaseClientAction(AbstractAction): """Action which sets config parameters for a database client on a node.""" - model_config = ConfigDict(extra="forbid") - server_ip_address: Optional[str] = None - server_password: Optional[str] = None - class _Opts(BaseModel): """Schema for options that can be passed to this action.""" @@ -204,7 +200,6 @@ class ConfigureRansomwareScriptAction(AbstractAction): class _Opts(BaseModel, AbstractAction.ConfigSchema): """Schema for options that can be passed to this option.""" - node_name: str model_config = ConfigDict(extra="forbid") server_ip_address: Optional[str] = None server_password: Optional[str] = None diff --git a/src/primaite/game/agent/actions/abstract.py b/src/primaite/game/agent/actions/abstract.py index 2ed168d9..c18f0dbc 100644 --- a/src/primaite/game/agent/actions/abstract.py +++ b/src/primaite/game/agent/actions/abstract.py @@ -43,6 +43,5 @@ class AbstractAction(BaseModel): def from_config(cls, config: Dict) -> "AbstractAction": """Create an action component from a config dictionary.""" for attribute, value in config.items(): - if not hasattr(cls.ConfigSchema, attribute): - setattr(cls.ConfigSchema, attribute, value) + setattr(cls.ConfigSchema, attribute, value) return cls diff --git a/src/primaite/game/agent/actions/acl.py b/src/primaite/game/agent/actions/acl.py index d6d5f4b4..3beface9 100644 --- a/src/primaite/game/agent/actions/acl.py +++ b/src/primaite/game/agent/actions/acl.py @@ -1,5 +1,5 @@ # © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK -from typing import List, Literal +from typing import List, Literal, Union from primaite.game.agent.actions.manager import AbstractAction from primaite.interface.request import RequestFormat @@ -92,6 +92,12 @@ class FirewallACLAddRuleAction(ACLAbstractAction, identifier="firewall_acl_add_r num_protocols: int num_permissions: int = 3 permission: str + target_firewall_nodename: str + src_ip: str + dst_ip: str + dst_wildcard: str + src_port: Union[int| None] + dst_port: Union[int | None] class ConfigSchema(ACLAbstractAction.ConfigSchema): """Configuration schema for FirewallACLAddRuleAction.""" @@ -102,29 +108,22 @@ class FirewallACLAddRuleAction(ACLAbstractAction, identifier="firewall_acl_add_r num_protocols: int num_permissions: int = 3 permission: str + target_firewall_nodename: str + src_ip: str + dst_ip: str + dst_wildcard: str + src_port: Union[int| None] @classmethod def form_request(cls, config: ConfigSchema) -> List[str]: """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - if config.protocol_id == 0: + if config.protocol_name == None: return ["do_nothing"] # NOT SUPPORTED, JUST DO NOTHING IF WE COME ACROSS THIS - if config.source_ip_id == 0: + if config.src_ip == 0: return ["do_nothing"] # invalid formulation - elif config.source_ip_id == 1: - src_ip = "ALL" - else: - # src_ip = self.manager.get_ip_address_by_idx(source_ip_id - 2) - # subtract 2 to account for UNUSED=0, and ALL=1 - pass + if config.src_port == 0: + return ["do_nothing"] # invalid configuration. - if config.source_port_id == 0: - return ["do_nothing"] # invalid formulation - elif config.source_port_id == 1: - src_port = "ALL" - else: - # src_port = self.manager.get_port_by_idx(source_port_id - 2) - # subtract 2 to account for UNUSED=0, and ALL=1 - pass return [ "network", @@ -136,9 +135,9 @@ class FirewallACLAddRuleAction(ACLAbstractAction, identifier="firewall_acl_add_r "add_rule", config.permission, config.protocol_name, - str(src_ip), + config.src_ip, config.src_wildcard, - src_port, + config.src_port, config.dst_ip, config.dst_wildcard, config.dst_port, diff --git a/src/primaite/game/agent/actions/config.py b/src/primaite/game/agent/actions/config.py index d7b436d7..dc7e98b9 100644 --- a/src/primaite/game/agent/actions/config.py +++ b/src/primaite/game/agent/actions/config.py @@ -37,7 +37,7 @@ class ConfigureRansomwareScriptAction(AbstractAction, identifier="c2_server_rans if config.node_name is None: return ["do_nothing"] ConfigureRansomwareScriptAction._Opts.model_validate(config) # check that options adhere to schema - return ["network", "node", config.node_name, "application", "RansomwareScript", "configure", config] + return ["network", "node", config.node_name, "application", "RansomwareScript", "configure", config.model_config] class ConfigureDoSBotAction(AbstractAction, identifier="configure_dos_bot"): diff --git a/tests/assets/configs/firewall_actions_network.yaml b/tests/assets/configs/firewall_actions_network.yaml index 88b09a29..a2b75be5 100644 --- a/tests/assets/configs/firewall_actions_network.yaml +++ b/tests/assets/configs/firewall_actions_network.yaml @@ -112,14 +112,14 @@ agents: firewall_port_name: internal firewall_port_direction: inbound position: 1 - permission: 1 - source_ip_id: 2 # client 1 - dest_ip_id: 1 # ALL - source_port_id: 1 - dest_port_id: 1 - protocol_id: 1 - source_wildcard_id: 0 - dest_wildcard_id: 0 + permission: PERMIT + src_ip: 192.168.0.10 + dst_ip: ALL + src_port: ALL + dst_port: ALL + protocol_name: ALL + src_wildcard: 0 + dst_wildcard: 0 2: action: firewall_acl_remove_rule options: @@ -134,12 +134,12 @@ agents: firewall_port_name: internal firewall_port_direction: outbound position: 1 - permission: 2 - source_ip_id: 2 # client 1 - dest_ip_id: 1 # ALL - source_port_id: 2 - dest_port_id: 3 - protocol_id: 2 + permission: DENY + src_ip: 192.168.0.10 # client 1 + dest_ip: ALL # ALL + src_port: ARP + dst_port: DNS + protocol_name: ICMP source_wildcard_id: 0 dest_wildcard_id: 0 4: @@ -156,12 +156,12 @@ agents: firewall_port_name: dmz firewall_port_direction: inbound position: 1 - permission: 2 - source_ip_id: 3 # dmz_server - dest_ip_id: 2 # client_1 - source_port_id: 4 - dest_port_id: 4 - protocol_id: 4 + permission: DENY + src_ip: 192.168.10.10 # dmz_server + dst_ip: 192.168.0.10 # client_1 + src_port: HTTP + dst_port: HTTP + protocol_name: UDP source_wildcard_id: 0 dest_wildcard_id: 0 6: @@ -178,12 +178,12 @@ agents: firewall_port_name: dmz firewall_port_direction: outbound position: 2 - permission: 2 - source_ip_id: 3 # dmz_server - dest_ip_id: 2 # client_1 - source_port_id: 4 - dest_port_id: 4 - protocol_id: 3 + permission: DENY + src_ip: 192.168.10.10 # dmz_server + dst_ip: 192.168.0.10 # client_1 + src_port: HTTP + dst_port: HTTP + protocol_name: TCP source_wildcard_id: 0 dest_wildcard_id: 0 8: @@ -200,12 +200,12 @@ agents: firewall_port_name: external firewall_port_direction: inbound position: 10 - permission: 2 - source_ip_id: 4 # external_computer - dest_ip_id: 3 # dmz - source_port_id: 5 - dest_port_id: 5 - protocol_id: 2 + permission: DENY + src_ip: 192.168.20.10 # external_computer + dst_ip: 192.168.10.10 # dmz + src_port: POSTGRES_SERVER + dst_port: POSTGRES_SERVER + protocol_name: ICMP source_wildcard_id: 0 dest_wildcard_id: 0 10: @@ -222,12 +222,12 @@ agents: firewall_port_name: external firewall_port_direction: outbound position: 1 - permission: 2 - source_ip_id: 4 # external_computer - dest_ip_id: 2 # client_1 - source_port_id: 1 - dest_port_id: 1 - protocol_id: 1 + permission: DENY + src_ip: 192.168.20.10 # external_computer + dst_ip: 192.168.0.10 # client_1 + src_port: NONE + dst_port: NONE + protocol_name: none source_wildcard_id: 0 dest_wildcard_id: 0 12: diff --git a/tests/integration_tests/game_layer/test_actions.py b/tests/integration_tests/game_layer/test_actions.py index f380ba7d..c4350e1f 100644 --- a/tests/integration_tests/game_layer/test_actions.py +++ b/tests/integration_tests/game_layer/test_actions.py @@ -163,8 +163,9 @@ def test_router_acl_addrule_integration(game_and_agent: Tuple[PrimaiteGame, Prox }, ) agent.store_action(action) + print(agent.most_recent_action) game.step() - + print(agent.most_recent_action) # 5: Check that the ACL now has 6 rules, but that server_1 can still ping server_2 print(router.acl.show()) assert router.acl.num_rules == 6 @@ -653,9 +654,9 @@ def test_firewall_acl_add_remove_rule_integration(): assert firewall.external_outbound_acl.acl[1].action.name == "DENY" assert firewall.external_outbound_acl.acl[1].src_ip_address == IPv4Address("192.168.20.10") assert firewall.external_outbound_acl.acl[1].dst_ip_address == IPv4Address("192.168.0.10") - assert firewall.external_outbound_acl.acl[1].dst_port is None - assert firewall.external_outbound_acl.acl[1].src_port is None - assert firewall.external_outbound_acl.acl[1].protocol is None + assert firewall.external_outbound_acl.acl[1].dst_port == PORT_LOOKUP["NONE"] + assert firewall.external_outbound_acl.acl[1].src_port == PORT_LOOKUP["NONE"] + assert firewall.external_outbound_acl.acl[1].protocol == PROTOCOL_LOOKUP["NONE"] env.step(12) # Remove ACL rule from External Outbound assert firewall.external_outbound_acl.num_rules == 1 From 4e7ca7a88a2ede458f2269ada0bf8a89f5eb8db4 Mon Sep 17 00:00:00 2001 From: Charlie Crane Date: Fri, 15 Nov 2024 16:49:16 +0000 Subject: [PATCH 37/58] #2912 - Removal of excess comments --- src/primaite/game/agent/actions/abstract.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/primaite/game/agent/actions/abstract.py b/src/primaite/game/agent/actions/abstract.py index c18f0dbc..9c13cc73 100644 --- a/src/primaite/game/agent/actions/abstract.py +++ b/src/primaite/game/agent/actions/abstract.py @@ -8,15 +8,6 @@ from pydantic import BaseModel, ConfigDict from primaite.interface.request import RequestFormat -# notes: -# we actually don't need to hold any state in actions, so there's no need to define any __init__ logic. -# all the init methods in the old actions are just used for holding a verb and shape, which are not really used. -# the config schema should be used to the actual parameters for formatting the action itself. -# (therefore there's no need for creating action instances, just the action class contains logic for converting -# CAOS actions to requests for simulator. Similar to the network node adder, that class also doesn't need to be -# instantiated.) - - class AbstractAction(BaseModel): """Base class for actions.""" From ce77df00ccf25d55d826fc8cf69303baad51cfb9 Mon Sep 17 00:00:00 2001 From: Charlie Crane Date: Fri, 15 Nov 2024 17:22:47 +0000 Subject: [PATCH 38/58] #2912 - Updated changelog --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e9147947..16cc3ec8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [4.0.0] = TBC + +### Added + +### Changed +- Actions within PrimAITE are now extensible, allowing for plugin support. + + ## [3.3.0] - 2024-09-04 ### Added From 0439c3159e5948a8a7a36501aa835840e39d4fe2 Mon Sep 17 00:00:00 2001 From: Charlie Crane Date: Fri, 15 Nov 2024 17:40:11 +0000 Subject: [PATCH 39/58] #2912 - Minor update to extensible actions documentation and moved old actions into _legacy --- docs/source/how_to_guides/extensible_actions.rst | 2 ++ src/primaite/{game/agent => _legacy}/actions.py | 0 2 files changed, 2 insertions(+) rename src/primaite/{game/agent => _legacy}/actions.py (100%) diff --git a/docs/source/how_to_guides/extensible_actions.rst b/docs/source/how_to_guides/extensible_actions.rst index bd78c8e1..576aa75f 100644 --- a/docs/source/how_to_guides/extensible_actions.rst +++ b/docs/source/how_to_guides/extensible_actions.rst @@ -25,8 +25,10 @@ New actions to be used within PrimAITE require: .. code-block:: python class ExampleAction(AbstractAction, identifier="Example_action"): + """An example action for demonstration purposes.""" class ConfigSchema(AbstractAction.ConfigSchema): + """The configuration schema with all attributes expected goes here.""" target_application: str The ConfigSchema is used when the class is called to form the action, within the `form_request` method, detailed below. diff --git a/src/primaite/game/agent/actions.py b/src/primaite/_legacy/actions.py similarity index 100% rename from src/primaite/game/agent/actions.py rename to src/primaite/_legacy/actions.py From 958502d055fcd66b353e0e30f687643452178753 Mon Sep 17 00:00:00 2001 From: Charlie Crane Date: Fri, 15 Nov 2024 17:47:58 +0000 Subject: [PATCH 40/58] #2912 - Removal of todo and updated actions __init__.py --- src/primaite/game/agent/actions/__init__.py | 2 -- src/primaite/game/agent/actions/manager.py | 4 +--- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/primaite/game/agent/actions/__init__.py b/src/primaite/game/agent/actions/__init__.py index 016a09ba..625725fe 100644 --- a/src/primaite/game/agent/actions/__init__.py +++ b/src/primaite/game/agent/actions/__init__.py @@ -14,7 +14,6 @@ from primaite.game.agent.actions import ( service, session, ) -from primaite.game.agent.actions.manager import ActionManager __all__ = ( "abstract", @@ -29,5 +28,4 @@ __all__ = ( "node", "service", "session", - "ActionManager", ) diff --git a/src/primaite/game/agent/actions/manager.py b/src/primaite/game/agent/actions/manager.py index 6c8353b0..3795d21d 100644 --- a/src/primaite/game/agent/actions/manager.py +++ b/src/primaite/game/agent/actions/manager.py @@ -22,9 +22,7 @@ from gymnasium import spaces from primaite.game.agent.actions.abstract import AbstractAction from primaite.interface.request import RequestFormat -# TODO: Make sure that actions are backwards compatible where the old YAML format is used. - -__all__ = "DoNothingAction" +__all__ = ("DoNothingAction", "ActionManager") class DoNothingAction(AbstractAction, identifier="do_nothing"): From b4bc59f6333bcb6b480e7fa62f8444492b36f0a9 Mon Sep 17 00:00:00 2001 From: Charlie Crane Date: Tue, 19 Nov 2024 18:57:40 +0000 Subject: [PATCH 41/58] #2912 - Updates to actions refactor to align with rewards refactor for consistency across codebase. --- src/primaite/game/agent/actions/__init__.py | 1 + src/primaite/game/agent/actions/abstract.py | 10 +- src/primaite/game/agent/actions/acl.py | 77 +++++++------ .../game/agent/actions/application.py | 23 ++-- src/primaite/game/agent/actions/config.py | 108 ++++++++---------- src/primaite/game/agent/actions/file.py | 32 +++--- src/primaite/game/agent/actions/folder.py | 19 +-- src/primaite/game/agent/actions/host_nic.py | 13 ++- src/primaite/game/agent/actions/manager.py | 7 +- src/primaite/game/agent/actions/network.py | 17 +-- src/primaite/game/agent/actions/node.py | 27 ++++- src/primaite/game/agent/actions/service.py | 25 ++-- src/primaite/game/agent/actions/session.py | 13 ++- .../configs/firewall_actions_network.yaml | 14 +-- .../game_layer/test_actions.py | 4 +- 15 files changed, 209 insertions(+), 181 deletions(-) diff --git a/src/primaite/game/agent/actions/__init__.py b/src/primaite/game/agent/actions/__init__.py index 625725fe..7f054591 100644 --- a/src/primaite/game/agent/actions/__init__.py +++ b/src/primaite/game/agent/actions/__init__.py @@ -14,6 +14,7 @@ from primaite.game.agent.actions import ( service, session, ) +from primaite.game.agent.actions.manager import ActionManager __all__ = ( "abstract", diff --git a/src/primaite/game/agent/actions/abstract.py b/src/primaite/game/agent/actions/abstract.py index 9c13cc73..5c0594fd 100644 --- a/src/primaite/game/agent/actions/abstract.py +++ b/src/primaite/game/agent/actions/abstract.py @@ -11,6 +11,8 @@ from primaite.interface.request import RequestFormat class AbstractAction(BaseModel): """Base class for actions.""" + config: "AbstractAction.ConfigSchema" + class ConfigSchema(BaseModel, ABC): """Base configuration schema for Actions.""" @@ -33,6 +35,8 @@ class AbstractAction(BaseModel): @classmethod def from_config(cls, config: Dict) -> "AbstractAction": """Create an action component from a config dictionary.""" - for attribute, value in config.items(): - setattr(cls.ConfigSchema, attribute, value) - return cls + if not config.get("type"): + config.update({"type": cls.__name__}) + print("oooh") + print(config) + return cls(config=cls.ConfigSchema(**config)) diff --git a/src/primaite/game/agent/actions/acl.py b/src/primaite/game/agent/actions/acl.py index 3beface9..11269a7e 100644 --- a/src/primaite/game/agent/actions/acl.py +++ b/src/primaite/game/agent/actions/acl.py @@ -25,27 +25,21 @@ class ACLAbstractAction(AbstractAction, identifier="acl_abstract_action"): class RouterACLAddRuleAction(AbstractAction, identifier="router_acl_add_rule"): """Action which adds a rule to a router's ACL.""" - target_router: str - position: int - permission: Literal[1, 2] - source_wildcard_id: int - source_port: str - dst_ip: str - dst_wildcard: int - dst_port: int + config: "RouterACLAddRuleAction.ConfigSchema" class ConfigSchema(AbstractAction.ConfigSchema): """Configuration Schema for RouterACLAddRuleAction.""" target_router: str + permission: str + protocol_name: str position: int - permission: Literal[1, 2] src_ip: str src_wildcard: int source_port: str dst_ip: str dst_wildcard: int - dst_port: int + dst_port: str @classmethod def form_request(cls, config: ConfigSchema) -> List[str]: @@ -71,11 +65,13 @@ class RouterACLAddRuleAction(AbstractAction, identifier="router_acl_add_rule"): class RouterACLRemoveRuleAction(AbstractAction, identifier="router_acl_remove_rule"): """Action which removes a rule from a router's ACL.""" + config: "RouterACLRemoveRuleAction.ConfigSchema" + class ConfigSchema(AbstractAction.ConfigSchema): """Configuration schema for RouterACLRemoveRuleAction.""" target_router: str - position: str + position: int @classmethod def form_request(cls, config: ConfigSchema) -> RequestFormat: @@ -86,33 +82,42 @@ class RouterACLRemoveRuleAction(AbstractAction, identifier="router_acl_remove_ru class FirewallACLAddRuleAction(ACLAbstractAction, identifier="firewall_acl_add_rule"): """Action which adds a rule to a firewall port's ACL.""" - max_acl_rules: int - num_ips: int - num_ports: int - num_protocols: int - num_permissions: int = 3 - permission: str - target_firewall_nodename: str - src_ip: str - dst_ip: str - dst_wildcard: str - src_port: Union[int| None] - dst_port: Union[int | None] + config: "FirewallACLAddRuleAction.ConfigSchema" + + # max_acl_rules: int + # num_ips: int + # num_ports: int + # num_protocols: int + # num_permissions: int = 3 + # permission: str + # target_firewall_nodename: str + # src_ip: str + # dst_ip: str + # dst_wildcard: str + # src_port: Union[int| None] + # dst_port: Union[int | None] class ConfigSchema(ACLAbstractAction.ConfigSchema): """Configuration schema for FirewallACLAddRuleAction.""" - max_acl_rules: int - num_ips: int - num_ports: int - num_protocols: int - num_permissions: int = 3 - permission: str target_firewall_nodename: str + firewall_port_name: str + firewall_port_direction: str + position: int + permission: str src_ip: str - dst_ip: str - dst_wildcard: str - src_port: Union[int| None] + dest_ip: str + src_port: str + dst_port: str + protocol_name: str + source_wildcard_id: int + dest_wildcard_id: int + + # max_acl_rules: int + # num_ips: int + # num_ports: int + # num_protocols: int + # num_permissions: int = 3 @classmethod def form_request(cls, config: ConfigSchema) -> List[str]: @@ -136,10 +141,10 @@ class FirewallACLAddRuleAction(ACLAbstractAction, identifier="firewall_acl_add_r config.permission, config.protocol_name, config.src_ip, - config.src_wildcard, + config.source_wildcard_id, config.src_port, - config.dst_ip, - config.dst_wildcard, + config.dest_ip, + config.dest_wildcard_id, config.dst_port, config.position, ] @@ -148,6 +153,8 @@ class FirewallACLAddRuleAction(ACLAbstractAction, identifier="firewall_acl_add_r class FirewallACLRemoveRuleAction(AbstractAction, identifier="firewall_acl_remove_rule"): """Action which removes a rule from a firewall port's ACL.""" + config:"FirewallACLRemoveRuleAction.ConfigSchema" + class ConfigSchema(AbstractAction.ConfigSchema): """Configuration schema for FirewallACLRemoveRuleAction.""" diff --git a/src/primaite/game/agent/actions/application.py b/src/primaite/game/agent/actions/application.py index 942ebe90..f515a8ec 100644 --- a/src/primaite/game/agent/actions/application.py +++ b/src/primaite/game/agent/actions/application.py @@ -22,13 +22,14 @@ class NodeApplicationAbstractAction(AbstractAction, identifier="node_application inherit from this base class. """ + config: "NodeApplicationAbstractAction.ConfigSchema" + class ConfigSchema(AbstractAction.ConfigSchema): """Base Configuration schema for Node Application actions.""" node_name: str application_name: str - - verb: ClassVar[str] + verb: ClassVar[str] @classmethod def form_request(cls, config: ConfigSchema) -> RequestFormat: @@ -41,14 +42,14 @@ class NodeApplicationAbstractAction(AbstractAction, identifier="node_application config.node_name, "application", config.application_name, - cls.model_fields["verb"].default, + config.verb, ] class NodeApplicationExecuteAction(NodeApplicationAbstractAction, identifier="node_application_execute"): """Action which executes an application.""" - verb: str = "execute" + config: "NodeApplicationExecuteAction.ConfigSchema" class ConfigSchema(NodeApplicationAbstractAction.ConfigSchema): """Configuration schema for NodeApplicationExecuteAction.""" @@ -59,7 +60,7 @@ class NodeApplicationExecuteAction(NodeApplicationAbstractAction, identifier="no class NodeApplicationScanAction(NodeApplicationAbstractAction, identifier="node_application_scan"): """Action which scans an application.""" - verb: str = "scan" + config: "NodeApplicationScanAction.ConfigSchema" class ConfigSchema(NodeApplicationAbstractAction.ConfigSchema): """Configuration schema for NodeApplicationScanAction.""" @@ -70,7 +71,7 @@ class NodeApplicationScanAction(NodeApplicationAbstractAction, identifier="node_ class NodeApplicationCloseAction(NodeApplicationAbstractAction, identifier="node_application_close"): """Action which closes an application.""" - verb: str = "close" + config: "NodeApplicationCloseAction.ConfigSchema" class ConfigSchema(NodeApplicationAbstractAction.ConfigSchema): """Configuration schema for NodeApplicationCloseAction.""" @@ -81,7 +82,7 @@ class NodeApplicationCloseAction(NodeApplicationAbstractAction, identifier="node class NodeApplicationFixAction(NodeApplicationAbstractAction, identifier="node_application_fix"): """Action which fixes an application.""" - verb: str = "fix" + config: "NodeApplicationFixAction.ConfigSchema" class ConfigSchema(NodeApplicationAbstractAction.ConfigSchema): """Configuration schema for NodeApplicationFixAction.""" @@ -92,7 +93,7 @@ class NodeApplicationFixAction(NodeApplicationAbstractAction, identifier="node_a class NodeApplicationInstallAction(NodeApplicationAbstractAction, identifier="node_application_install"): """Action which installs an application.""" - verb: str = "install" + config: "NodeApplicationInstallAction.ConfigSchema" class ConfigSchema(NodeApplicationAbstractAction.ConfigSchema): """Configuration schema for NodeApplicationInstallAction.""" @@ -110,7 +111,7 @@ class NodeApplicationInstallAction(NodeApplicationAbstractAction, identifier="no config.node_name, "software_manager", "application", - cls.model_fields["verb"].default, + config.verb, config.application_name, ] @@ -118,7 +119,7 @@ class NodeApplicationInstallAction(NodeApplicationAbstractAction, identifier="no class NodeApplicationRemoveAction(NodeApplicationAbstractAction, identifier="node_application_remove"): """Action which removes/uninstalls an application.""" - verb: str = "uninstall" + config: "NodeApplicationRemoveAction.ConfigSchema" class ConfigSchema(NodeApplicationAbstractAction.ConfigSchema): """Configuration schema for NodeApplicationRemoveAction.""" @@ -136,6 +137,6 @@ class NodeApplicationRemoveAction(NodeApplicationAbstractAction, identifier="nod config.node_name, "software_manager", "application", - cls.model_fields["verb"].default, + config.verb, config.application_name, ] diff --git a/src/primaite/game/agent/actions/config.py b/src/primaite/game/agent/actions/config.py index dc7e98b9..da9f77e6 100644 --- a/src/primaite/game/agent/actions/config.py +++ b/src/primaite/game/agent/actions/config.py @@ -22,6 +22,8 @@ __all__ = ( class ConfigureRansomwareScriptAction(AbstractAction, identifier="c2_server_ransomware_configure"): """Action which sets config parameters for a ransomware script on a node.""" + config: "ConfigureRansomwareScriptAction.ConfigSchema" + class ConfigSchema(AbstractAction.ConfigSchema): """Configuration schema for ConfigureRansomwareScriptAction.""" @@ -36,16 +38,18 @@ class ConfigureRansomwareScriptAction(AbstractAction, identifier="c2_server_rans """Return the action formatted as a request that can be ingested by the simulation.""" if config.node_name is None: return ["do_nothing"] - ConfigureRansomwareScriptAction._Opts.model_validate(config) # check that options adhere to schema return ["network", "node", config.node_name, "application", "RansomwareScript", "configure", config.model_config] class ConfigureDoSBotAction(AbstractAction, identifier="configure_dos_bot"): """Action which sets config parameters for a DoS bot on a node.""" - class _Opts(BaseModel): + config: "ConfigureDoSBotAction.ConfigSchema" + + class ConfigSchema(AbstractAction.ConfigSchema): """Schema for options that can be passed to this action.""" + node_name: str model_config = ConfigDict(extra="forbid") target_ip_address: Optional[str] = None target_port: Optional[str] = None @@ -58,18 +62,19 @@ class ConfigureDoSBotAction(AbstractAction, identifier="configure_dos_bot"): def __init__(self, manager: "ActionManager", **kwargs) -> None: super().__init__(manager=manager) - def form_request(self, node_id: int, config: Dict) -> RequestFormat: + def form_request(self, config: ConfigSchema) -> RequestFormat: """Return the action formatted as a request that can be ingested by the simulation.""" - node_name = self.manager.get_node_name_by_idx(node_id) - if node_name is None: + if config.node_name is None: return ["do_nothing"] - self._Opts.model_validate(config) # check that options adhere to schema - return ["network", "node", node_name, "application", "DoSBot", "configure", config] + self.ConfigSchema.model_validate(config) # check that options adhere to schema + return ["network", "node", config.node_name, "application", "DoSBot", "configure", config] class ConfigureC2BeaconAction(AbstractAction, identifier="configure_c2_beacon"): """Action which configures a C2 Beacon based on the parameters given.""" + config: "ConfigureC2BeaconAction.ConfigSchema" + class ConfigSchema(AbstractAction.ConfigSchema): """Configuration schema for ConfigureC2BeaconAction.""" @@ -79,14 +84,6 @@ class ConfigureC2BeaconAction(AbstractAction, identifier="configure_c2_beacon"): masquerade_protocol: str = Field(default="TCP") masquerade_port: str = Field(default="HTTP") - class _Opts(BaseModel): - """Schema for options that can be passed to this action.""" - - c2_server_ip_address: str - keep_alive_frequency: int = Field(default=5, ge=1) - masquerade_protocol: str = Field(default="TCP") - masquerade_port: str = Field(default="HTTP") - @field_validator( "c2_server_ip_address", "keep_alive_frequency", @@ -106,21 +103,23 @@ class ConfigureC2BeaconAction(AbstractAction, identifier="configure_c2_beacon"): """Return the action formatted as a request that can be ingested by the simulation.""" if config.node_name is None: return ["do_nothing"] - configuration = ConfigureC2BeaconAction._Opts( + configuration = ConfigureC2BeaconAction.ConfigSchema( c2_server_ip_address=config.c2_server_ip_address, keep_alive_frequency=config.keep_alive_frequency, masquerade_port=config.masquerade_port, masquerade_protocol=config.masquerade_protocol, ) - ConfigureC2BeaconAction._Opts.model_validate(configuration) # check that options adhere to schema + ConfigureC2BeaconAction.ConfigSchema.model_validate(configuration) # check that options adhere to schema - return ["network", "node", config.node_name, "application", "C2Beacon", "configure", config.__dict__] + return ["network", "node", config.node_name, "application", "C2Beacon", "configure", configuration] class NodeSendRemoteCommandAction(AbstractAction, identifier="node_send_remote_command"): """Action which sends a terminal command to a remote node via SSH.""" + config: "NodeSendRemoteCommandAction.ConfigSchema" + class ConfigSchema(AbstractAction.ConfigSchema): """Configuration schema for NodeSendRemoteCommandAction.""" @@ -146,37 +145,37 @@ class NodeSendRemoteCommandAction(AbstractAction, identifier="node_send_remote_c class TerminalC2ServerAction(AbstractAction, identifier="c2_server_terminal_command"): """Action which causes the C2 Server to send a command to the C2 Beacon to execute the terminal command passed.""" - class _Opts(BaseModel): + config: "TerminalC2ServerAction.ConfigSchema" + + class ConfigSchema(AbstractAction.ConfigSchema): """Schema for options that can be passed to this action.""" + node_name: str commands: Union[List[RequestFormat], RequestFormat] ip_address: Optional[str] username: Optional[str] password: Optional[str] - def __init__(self, manager: "ActionManager", **kwargs) -> None: - super().__init__(manager=manager) - - def form_request(self, node_id: int, commands: List, ip_address: Optional[str], account: dict) -> RequestFormat: + @classmethod + def form_request(cls, config: ConfigSchema) -> RequestFormat: """Return the action formatted as a request that can be ingested by the simulation.""" - node_name = self.manager.get_node_name_by_idx(node_id) - if node_name is None: + if config.node_name is None: return ["do_nothing"] command_model = { - "commands": commands, - "ip_address": ip_address, - "username": account["username"], - "password": account["password"], + "commands": config.commands, + "ip_address": config.ip_address, + "username": config.username, + "password": config.password, } - - TerminalC2ServerAction._Opts.model_validate(command_model) - return ["network", "node", node_name, "application", "C2Server", "terminal_command", command_model] + return ["network", "node", config.node_name, "application", "C2Server", "terminal_command", command_model] class RansomwareLaunchC2ServerAction(AbstractAction, identifier="c2_server_ransomware_launch"): """Action which causes the C2 Server to send a command to the C2 Beacon to launch the RansomwareScript.""" + config: "RansomwareLaunchC2ServerAction.ConfigSchema" + class ConfigSchema(AbstractAction.ConfigSchema): """Configuration schema for RansomwareLaunchC2ServerAction.""" @@ -194,9 +193,12 @@ class RansomwareLaunchC2ServerAction(AbstractAction, identifier="c2_server_ranso class ExfiltrationC2ServerAction(AbstractAction, identifier="c2_server_data_exfiltrate"): """Action which exfiltrates a target file from a certain node onto the C2 beacon and then the C2 Server.""" - class _Opts(BaseModel): + config: "ExfiltrationC2ServerAction.ConfigSchema" + + class ConfigSchema(AbstractAction.ConfigSchema): """Schema for options that can be passed to this action.""" + node_name: str username: Optional[str] password: Optional[str] target_ip_address: str @@ -204,40 +206,30 @@ class ExfiltrationC2ServerAction(AbstractAction, identifier="c2_server_data_exfi target_folder_name: str exfiltration_folder_name: Optional[str] - def __init__(self, manager: "ActionManager", **kwargs) -> None: - super().__init__(manager=manager) - + @classmethod def form_request( - self, - node_id: int, - account: dict, - target_ip_address: str, - target_file_name: str, - target_folder_name: str, - exfiltration_folder_name: Optional[str], + cls, + config: ConfigSchema ) -> RequestFormat: """Return the action formatted as a request that can be ingested by the simulation.""" - node_name = self.manager.get_node_name_by_idx(node_id) - if node_name is None: + if config.node_name is None: return ["do_nothing"] command_model = { - "target_file_name": target_file_name, - "target_folder_name": target_folder_name, - "exfiltration_folder_name": exfiltration_folder_name, - "target_ip_address": target_ip_address, - "username": account["username"], - "password": account["password"], + "target_file_name": config.target_file_name, + "target_folder_name": config.target_folder_name, + "exfiltration_folder_name": config.exfiltration_folder_name, + "target_ip_address": config.target_ip_address, + "username": config.username, + "password": config.password, } - ExfiltrationC2ServerAction._Opts.model_validate(command_model) - return ["network", "node", node_name, "application", "C2Server", "exfiltrate", command_model] + return ["network", "node", config.node_name, "application", "C2Server", "exfiltrate", command_model] class ConfigureDatabaseClientAction(AbstractAction, identifier="configure_database_client"): """Action which sets config parameters for a database client on a node.""" - node_name: str - model_config: ConfigDict = ConfigDict(extra="forbid") + config: "ConfigureDatabaseClientAction.ConfigSchema" class ConfigSchema(AbstractAction.ConfigSchema): """Schema for options that can be passed to this action.""" @@ -245,10 +237,8 @@ class ConfigureDatabaseClientAction(AbstractAction, identifier="configure_databa node_name: str model_config = ConfigDict(extra="forbid") - def __init__(self, manager: "ActionManager", **kwargs) -> None: - super().__init__(manager=manager) - - def form_request(self, config: ConfigSchema) -> RequestFormat: + @classmethod + def form_request(cls, config: ConfigSchema) -> RequestFormat: """Return the action formatted as a request that can be ingested by the simulation.""" if config.node_name is None: return ["do_nothing"] diff --git a/src/primaite/game/agent/actions/file.py b/src/primaite/game/agent/actions/file.py index 5d12b27a..b5e47c8a 100644 --- a/src/primaite/game/agent/actions/file.py +++ b/src/primaite/game/agent/actions/file.py @@ -23,14 +23,15 @@ class NodeFileAbstractAction(AbstractAction, identifier="node_file_abstract_acti only three parameters can inherit from this base class. """ + config: "NodeFileAbstractAction.ConfigSchema" + class ConfigSchema(AbstractAction.ConfigSchema): """Configuration Schema for NodeFileAbstractAction.""" node_name: str folder_name: str file_name: str - - verb: ClassVar[str] + verb: ClassVar[str] @classmethod def form_request(cls, config: ConfigSchema) -> RequestFormat: @@ -46,15 +47,14 @@ class NodeFileAbstractAction(AbstractAction, identifier="node_file_abstract_acti config.folder_name, "file", config.file_name, - cls.model_fields["verb"].default, + config.verb, ] class NodeFileCreateAction(NodeFileAbstractAction, identifier="node_file_create"): """Action which creates a new file in a given folder.""" - verb: str = "create" - force: bool = False + config: "NodeFileCreateAction.ConfigSchema" class ConfigSchema(NodeFileAbstractAction.ConfigSchema): """Configuration schema for NodeFileCreateAction.""" @@ -72,18 +72,18 @@ class NodeFileCreateAction(NodeFileAbstractAction, identifier="node_file_create" "node", config.node_name, "file_system", - cls.model_fields["verb"].default, + config.verb, "file", config.folder_name, config.file_name, - cls.model_fields["force"].default, + config.verb, ] class NodeFileScanAction(NodeFileAbstractAction, identifier="node_file_scan"): """Action which scans a file.""" - verb: str = "scan" + config: "NodeFileScanAction.ConfigSchema" class ConfigSchema(NodeFileAbstractAction.ConfigSchema): """Configuration schema for NodeFileScanAction.""" @@ -94,7 +94,7 @@ class NodeFileScanAction(NodeFileAbstractAction, identifier="node_file_scan"): class NodeFileDeleteAction(NodeFileAbstractAction, identifier="node_file_delete"): """Action which deletes a file.""" - verb: str = "delete" + config: "NodeFileDeleteAction.ConfigSchema" class ConfigSchema(NodeFileAbstractAction.ConfigSchema): """Configuration schema for NodeFileDeleteAction.""" @@ -111,7 +111,7 @@ class NodeFileDeleteAction(NodeFileAbstractAction, identifier="node_file_delete" "node", config.node_name, "file_system", - cls.model_fields["verb"].default, + config.verb, "file", config.folder_name, config.file_name, @@ -121,7 +121,7 @@ class NodeFileDeleteAction(NodeFileAbstractAction, identifier="node_file_delete" class NodeFileRestoreAction(NodeFileAbstractAction, identifier="node_file_restore"): """Action which restores a file.""" - verb: str = "restore" + config: "NodeFileRestoreAction.ConfigSchema" class ConfigSchema(NodeFileAbstractAction.ConfigSchema): """Configuration schema for NodeFileRestoreAction.""" @@ -132,7 +132,7 @@ class NodeFileRestoreAction(NodeFileAbstractAction, identifier="node_file_restor class NodeFileCorruptAction(NodeFileAbstractAction, identifier="node_file_corrupt"): """Action which corrupts a file.""" - verb: str = "corrupt" + config: "NodeFileCorruptAction.ConfigSchema" class ConfigSchema(NodeFileAbstractAction.ConfigSchema): """Configuration schema for NodeFileCorruptAction.""" @@ -143,7 +143,7 @@ class NodeFileCorruptAction(NodeFileAbstractAction, identifier="node_file_corrup class NodeFileAccessAction(NodeFileAbstractAction, identifier="node_file_access"): """Action which increases a file's access count.""" - verb: str = "access" + config: "NodeFileAccessAction.ConfigSchema" class ConfigSchema(NodeFileAbstractAction.ConfigSchema): """Configuration schema for NodeFileAccessAction.""" @@ -160,7 +160,7 @@ class NodeFileAccessAction(NodeFileAbstractAction, identifier="node_file_access" "node", config.node_name, "file_system", - cls.model_fields["verb"].default, + config.verb, config.folder_name, config.file_name, ] @@ -169,7 +169,7 @@ class NodeFileAccessAction(NodeFileAbstractAction, identifier="node_file_access" class NodeFileCheckhashAction(NodeFileAbstractAction, identifier="node_file_checkhash"): """Action which checks the hash of a file.""" - verb: str = "checkhash" + config: "NodeFileCheckhashAction.ConfigSchema" class ConfigSchema(NodeFileAbstractAction.ConfigSchema): """Configuration schema for NodeFileCheckhashAction.""" @@ -180,7 +180,7 @@ class NodeFileCheckhashAction(NodeFileAbstractAction, identifier="node_file_chec class NodeFileRepairAction(NodeFileAbstractAction, identifier="node_file_repair"): """Action which repairs a file.""" - verb: str = "repair" + config: "NodeFileRepairAction.ConfigSchema" class ConfigSchema(NodeFileAbstractAction.ConfigSchema): """Configuration Schema for NodeFileRepairAction.""" diff --git a/src/primaite/game/agent/actions/folder.py b/src/primaite/game/agent/actions/folder.py index e430efb7..a27ca89b 100644 --- a/src/primaite/game/agent/actions/folder.py +++ b/src/primaite/game/agent/actions/folder.py @@ -21,13 +21,14 @@ class NodeFolderAbstractAction(AbstractAction, identifier="node_folder_abstract" this base class. """ + config: "NodeFolderAbstractAction.ConfigSchema" + class ConfigSchema(AbstractAction.ConfigSchema): """Base configuration schema for NodeFolder actions.""" node_name: str folder_name: str - - verb: ClassVar[str] + verb: ClassVar[str] @classmethod def form_request(cls, config: ConfigSchema) -> RequestFormat: @@ -41,14 +42,14 @@ class NodeFolderAbstractAction(AbstractAction, identifier="node_folder_abstract" "file_system", "folder", config.folder_name, - cls.model_fields["verb"].default, + config.verb, ] class NodeFolderScanAction(NodeFolderAbstractAction, identifier="node_folder_scan"): """Action which scans a folder.""" - verb: str = "scan" + config: "NodeFolderScanAction.ConfigSchema" class ConfigSchema(NodeFolderAbstractAction.ConfigSchema): """Configuration schema for NodeFolderScanAction.""" @@ -59,7 +60,7 @@ class NodeFolderScanAction(NodeFolderAbstractAction, identifier="node_folder_sca class NodeFolderCheckhashAction(NodeFolderAbstractAction, identifier="node_folder_checkhash"): """Action which checks the hash of a folder.""" - verb: str = "checkhash" + config: "NodeFolderCheckhashAction.ConfigSchema" class ConfigSchema(NodeFolderAbstractAction.ConfigSchema): """Configuration schema for NodeFolderCheckhashAction.""" @@ -70,7 +71,7 @@ class NodeFolderCheckhashAction(NodeFolderAbstractAction, identifier="node_folde class NodeFolderRepairAction(NodeFolderAbstractAction, identifier="node_folder_repair"): """Action which repairs a folder.""" - verb: str = "repair" + config: "NodeFolderRepairAction.ConfigSchema" class ConfigSchema(NodeFolderAbstractAction.ConfigSchema): """Configuration schema for NodeFolderRepairAction.""" @@ -81,7 +82,7 @@ class NodeFolderRepairAction(NodeFolderAbstractAction, identifier="node_folder_r class NodeFolderRestoreAction(NodeFolderAbstractAction, identifier="node_folder_restore"): """Action which restores a folder.""" - verb: str = "restore" + config: "NodeFolderRestoreAction.ConfigSchema" class ConfigSchema(NodeFolderAbstractAction.ConfigSchema): """Configuration schema for NodeFolderRestoreAction.""" @@ -92,7 +93,7 @@ class NodeFolderRestoreAction(NodeFolderAbstractAction, identifier="node_folder_ class NodeFolderCreateAction(NodeFolderAbstractAction, identifier="node_folder_create"): """Action which creates a new folder.""" - verb: str = "create" + config: "NodeFolderCreateAction.ConfigSchema" class ConfigSchema(NodeFolderAbstractAction.ConfigSchema): """Configuration schema for NodeFolderCreateAction.""" @@ -109,7 +110,7 @@ class NodeFolderCreateAction(NodeFolderAbstractAction, identifier="node_folder_c "node", config.node_name, "file_system", - cls.model_fields["verb"].default, + config.verb, "folder", config.folder_name, ] diff --git a/src/primaite/game/agent/actions/host_nic.py b/src/primaite/game/agent/actions/host_nic.py index 1ad2e52f..6df241bc 100644 --- a/src/primaite/game/agent/actions/host_nic.py +++ b/src/primaite/game/agent/actions/host_nic.py @@ -1,4 +1,5 @@ # © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +from typing import ClassVar from primaite.game.agent.actions.manager import AbstractAction from primaite.interface.request import RequestFormat @@ -13,14 +14,14 @@ class HostNICAbstractAction(AbstractAction, identifier="host_nic_abstract"): class. """ - node_name: str - nic_num: str + config: "HostNICAbstractAction.ConfigSchema" class ConfigSchema(AbstractAction.ConfigSchema): """Base Configuration schema for HostNIC actions.""" node_name: str - nic_num: str + nic_num: int + verb: ClassVar[str] @classmethod def form_request(cls, config: ConfigSchema) -> RequestFormat: @@ -33,14 +34,14 @@ class HostNICAbstractAction(AbstractAction, identifier="host_nic_abstract"): config.node_name, "network_interface", config.nic_num, - cls.model_fields["verb"].default, + config.verb, ] class HostNICEnableAction(HostNICAbstractAction, identifier="host_nic_enable"): """Action which enables a NIC.""" - verb: str = "enable" + config: "HostNICEnableAction.ConfigSchema" class ConfigSchema(HostNICAbstractAction.ConfigSchema): """Configuration schema for HostNICEnableAction.""" @@ -51,7 +52,7 @@ class HostNICEnableAction(HostNICAbstractAction, identifier="host_nic_enable"): class HostNICDisableAction(HostNICAbstractAction, identifier="host_nic_disable"): """Action which disables a NIC.""" - verb: str = "disable" + config: "HostNICDisableAction.ConfigSchema" class ConfigSchema(HostNICAbstractAction.ConfigSchema): """Configuration schema for HostNICDisableAction.""" diff --git a/src/primaite/game/agent/actions/manager.py b/src/primaite/game/agent/actions/manager.py index 3795d21d..9ef94069 100644 --- a/src/primaite/game/agent/actions/manager.py +++ b/src/primaite/game/agent/actions/manager.py @@ -31,7 +31,8 @@ class DoNothingAction(AbstractAction, identifier="do_nothing"): class ConfigSchema(AbstractAction.ConfigSchema): """Configuration Schema for DoNothingAction.""" - type: Literal["do_nothing"] = "do_nothing" + # type: Literal["do_nothing"] = "do_nothing" + type: str = "do_nothing" @classmethod def form_request(cls, config: ConfigSchema) -> RequestFormat: @@ -126,13 +127,13 @@ class ActionManager: def form_request(self, action_identifier: str, action_options: Dict) -> RequestFormat: """Take action in CAOS format and use the execution definition to change it into PrimAITE request format.""" act_obj = self.actions[action_identifier].from_config(config=action_options) - return act_obj.form_request(config=act_obj.ConfigSchema) + return act_obj.form_request(config=act_obj.config) @property def space(self) -> spaces.Space: """Return the gymnasium action space for this agent.""" return spaces.Discrete(len(self.action_map)) - + @classmethod def from_config(cls, game: "PrimaiteGame", cfg: Dict) -> "ActionManager": """ diff --git a/src/primaite/game/agent/actions/network.py b/src/primaite/game/agent/actions/network.py index af3793a2..346da9b7 100644 --- a/src/primaite/game/agent/actions/network.py +++ b/src/primaite/game/agent/actions/network.py @@ -11,13 +11,14 @@ __all__ = ("NetworkPortEnableAction", "NetworkPortDisableAction") class NetworkPortAbstractAction(AbstractAction, identifier="network_port_abstract"): """Base class for Network port actions.""" + config: "NetworkPortAbstractAction.ConfigSchema" + class ConfigSchema(AbstractAction.ConfigSchema): """Base configuration schema for NetworkPort actions.""" target_nodename: str - port_id: str - - verb: ClassVar[str] + port_id: int + verb: ClassVar[str] @classmethod def form_request(cls, config: ConfigSchema) -> RequestFormat: @@ -30,16 +31,16 @@ class NetworkPortAbstractAction(AbstractAction, identifier="network_port_abstrac config.target_nodename, "network_interface", config.port_id, - cls.model_fields["verb"].default, + config.verb, ] class NetworkPortEnableAction(NetworkPortAbstractAction, identifier="network_port_enable"): """Action which enables are port on a router or a firewall.""" - verb: str = "enable" + config: "NetworkPortEnableAction.ConfigSchema" - class ConfigSchema(AbstractAction.ConfigSchema): + class ConfigSchema(NetworkPortAbstractAction.ConfigSchema): """Configuration schema for NetworkPortEnableAction.""" verb: str = "enable" @@ -48,9 +49,9 @@ class NetworkPortEnableAction(NetworkPortAbstractAction, identifier="network_por class NetworkPortDisableAction(NetworkPortAbstractAction, identifier="network_port_disable"): """Action which disables are port on a router or a firewall.""" - verb: str = "disable" + config: "NetworkPortDisableAction.ConfigSchema" - class ConfigSchema(AbstractAction.ConfigSchema): + class ConfigSchema(NetworkPortAbstractAction.ConfigSchema): """Configuration schema for NetworkPortDisableAction.""" verb: str = "disable" diff --git a/src/primaite/game/agent/actions/node.py b/src/primaite/game/agent/actions/node.py index a69a8a5f..3c70d495 100644 --- a/src/primaite/game/agent/actions/node.py +++ b/src/primaite/game/agent/actions/node.py @@ -23,22 +23,25 @@ class NodeAbstractAction(AbstractAction, identifier="node_abstract"): Any action which applies to a node and uses node_name as its only parameter can inherit from this base class. """ + config: "NodeAbstractAction.ConfigSchema" + class ConfigSchema(AbstractAction.ConfigSchema): """Base Configuration schema for Node actions.""" node_name: str - - verb: ClassVar[str] + verb: ClassVar[str] @classmethod def form_request(cls, config: ConfigSchema) -> RequestFormat: """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - return ["network", "node", config.node_name, cls.verb] + return ["network", "node", config.node_name, cls.config.verb] class NodeOSScanAction(NodeAbstractAction, identifier="node_os_scan"): """Action which scans a node's OS.""" + config: "NodeOSScanAction.ConfigSchema" + class ConfigSchema(NodeAbstractAction.ConfigSchema): """Configuration schema for NodeOSScanAction.""" @@ -48,6 +51,8 @@ class NodeOSScanAction(NodeAbstractAction, identifier="node_os_scan"): class NodeShutdownAction(NodeAbstractAction, identifier="node_shutdown"): """Action which shuts down a node.""" + config: "NodeShutdownAction.ConfigSchema" + class ConfigSchema(NodeAbstractAction.ConfigSchema): """Configuration schema for NodeShutdownAction.""" @@ -57,6 +62,8 @@ class NodeShutdownAction(NodeAbstractAction, identifier="node_shutdown"): class NodeStartupAction(NodeAbstractAction, identifier="node_startup"): """Action which starts up a node.""" + config: "NodeStartupAction.ConfigSchema" + class ConfigSchema(NodeAbstractAction.ConfigSchema): """Configuration schema for NodeStartupAction.""" @@ -66,6 +73,8 @@ class NodeStartupAction(NodeAbstractAction, identifier="node_startup"): class NodeResetAction(NodeAbstractAction, identifier="node_reset"): """Action which resets a node.""" + config: "NodeResetAction.ConfigSchema" + class ConfigSchema(NodeAbstractAction.ConfigSchema): """Configuration schema for NodeResetAction.""" @@ -75,22 +84,28 @@ class NodeResetAction(NodeAbstractAction, identifier="node_reset"): class NodeNMAPAbstractAction(AbstractAction, identifier="node_nmap_abstract_action"): """Base class for NodeNMAP actions.""" + config: "NodeNMAPAbstractAction.ConfigSchema" + class ConfigSchema(AbstractAction.ConfigSchema): """Base Configuration Schema for NodeNMAP actions.""" target_ip_address: Union[str, List[str]] - show: bool = False + show: bool = False node_name: str @classmethod @abstractmethod def form_request(cls, config: ConfigSchema) -> RequestFormat: + # NMAP action requests don't share a common format for their requests + # This is just a placeholder to ensure the method is defined. pass class NodeNMAPPingScanAction(NodeNMAPAbstractAction, identifier="node_nmap_ping_scan"): """Action which performs an NMAP ping scan.""" + config: "NodeNMAPPingScanAction.ConfigSchema" + class ConfigSchema(NodeNMAPAbstractAction.ConfigSchema): """Configuration schema for NodeNMAPPingScanAction.""" @@ -113,6 +128,8 @@ class NodeNMAPPingScanAction(NodeNMAPAbstractAction, identifier="node_nmap_ping_ class NodeNMAPPortScanAction(NodeNMAPAbstractAction, identifier="node_nmap_port_scan"): """Action which performs an NMAP port scan.""" + config: "NodeNMAPPortScanAction.ConfigSchema" + class ConfigSchema(NodeNMAPAbstractAction.ConfigSchema): """Configuration Schema for NodeNMAPPortScanAction.""" @@ -146,6 +163,8 @@ class NodeNMAPPortScanAction(NodeNMAPAbstractAction, identifier="node_nmap_port_ class NodeNetworkServiceReconAction(NodeNMAPAbstractAction, identifier="node_network_service_recon"): """Action which performs an NMAP network service recon (ping scan followed by port scan).""" + config: "NodeNetworkServiceReconAction.ConfigSchema" + class ConfigSchema(AbstractAction.ConfigSchema): """Configuration schema for NodeNetworkServiceReconAction.""" diff --git a/src/primaite/game/agent/actions/service.py b/src/primaite/game/agent/actions/service.py index dbdd57d3..7ccffb0a 100644 --- a/src/primaite/game/agent/actions/service.py +++ b/src/primaite/game/agent/actions/service.py @@ -23,22 +23,23 @@ class NodeServiceAbstractAction(AbstractAction, identifier="node_service_abstrac Any actions which use node_name and service_name can inherit from this class. """ + config: "NodeServiceAbstractAction.ConfigSchema" + class ConfigSchema(AbstractAction.ConfigSchema): node_name: str service_name: str - - verb: ClassVar[str] + verb: ClassVar[str] @classmethod def form_request(cls, config: ConfigSchema) -> RequestFormat: """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - return ["network", "node", config.node_name, "service", config.service_name, cls.model_fields["verb"].default] + return ["network", "node", config.node_name, "service", config.service_name, config.verb] class NodeServiceScanAction(NodeServiceAbstractAction, identifier="node_service_scan"): """Action which scans a service.""" - verb: str = "scan" + config: "NodeServiceScanAction.ConfigSchema" class ConfigSchema(NodeServiceAbstractAction.ConfigSchema): """Configuration Schema for NodeServiceScanAction.""" @@ -49,7 +50,7 @@ class NodeServiceScanAction(NodeServiceAbstractAction, identifier="node_service_ class NodeServiceStopAction(NodeServiceAbstractAction, identifier="node_service_stop"): """Action which stops a service.""" - verb: str = "stop" + config: "NodeServiceStopAction.ConfigSchema" class ConfigSchema(NodeServiceAbstractAction.ConfigSchema): """Configuration Schema for NodeServiceStopAction.""" @@ -60,7 +61,7 @@ class NodeServiceStopAction(NodeServiceAbstractAction, identifier="node_service_ class NodeServiceStartAction(NodeServiceAbstractAction, identifier="node_service_start"): """Action which starts a service.""" - verb: str = "start" + config: "NodeServiceStartAction.ConfigSchema" class ConfigSchema(NodeServiceAbstractAction.ConfigSchema): """Configuration Schema for NodeServiceStartAction.""" @@ -71,7 +72,7 @@ class NodeServiceStartAction(NodeServiceAbstractAction, identifier="node_service class NodeServicePauseAction(NodeServiceAbstractAction, identifier="node_service_pause"): """Action which pauses a service.""" - verb: str = "pause" + config: "NodeServicePauseAction.ConfigSchema" class ConfigSchema(NodeServiceAbstractAction.ConfigSchema): """Configuration Schema for NodeServicePauseAction.""" @@ -82,7 +83,7 @@ class NodeServicePauseAction(NodeServiceAbstractAction, identifier="node_service class NodeServiceResumeAction(NodeServiceAbstractAction, identifier="node_service_resume"): """Action which resumes a service.""" - verb: str = "resume" + config: "NodeServiceResumeAction.ConfigSchema" class ConfigSchema(NodeServiceAbstractAction.ConfigSchema): """Configuration Schema for NodeServiceResumeAction.""" @@ -93,7 +94,7 @@ class NodeServiceResumeAction(NodeServiceAbstractAction, identifier="node_servic class NodeServiceRestartAction(NodeServiceAbstractAction, identifier="node_service_restart"): """Action which restarts a service.""" - verb: str = "restart" + config: "NodeServiceRestartAction.ConfigSchema" class ConfigSchema(NodeServiceAbstractAction.ConfigSchema): """Configuration Schema for NodeServiceRestartAction.""" @@ -104,7 +105,7 @@ class NodeServiceRestartAction(NodeServiceAbstractAction, identifier="node_servi class NodeServiceDisableAction(NodeServiceAbstractAction, identifier="node_service_disable"): """Action which disables a service.""" - verb: str = "disable" + config: "NodeServiceDisableAction.ConfigSchema" class ConfigSchema(NodeServiceAbstractAction.ConfigSchema): """Configuration Schema for NodeServiceDisableAction.""" @@ -115,7 +116,7 @@ class NodeServiceDisableAction(NodeServiceAbstractAction, identifier="node_servi class NodeServiceEnableAction(NodeServiceAbstractAction, identifier="node_service_enable"): """Action which enables a service.""" - verb: str = "enable" + config: "NodeServiceEnableAction.ConfigSchema" class ConfigSchema(NodeServiceAbstractAction.ConfigSchema): """Configuration Schema for NodeServiceEnableAction.""" @@ -126,7 +127,7 @@ class NodeServiceEnableAction(NodeServiceAbstractAction, identifier="node_servic class NodeServiceFixAction(NodeServiceAbstractAction, identifier="node_service_fix"): """Action which fixes a service.""" - verb: str = "fix" + config: "NodeServiceFixAction.ConfigSchema" class ConfigSchema(NodeServiceAbstractAction.ConfigSchema): """Configuration Schema for NodeServiceFixAction.""" diff --git a/src/primaite/game/agent/actions/session.py b/src/primaite/game/agent/actions/session.py index dcae8b47..a0805a49 100644 --- a/src/primaite/game/agent/actions/session.py +++ b/src/primaite/game/agent/actions/session.py @@ -14,6 +14,8 @@ __all__ = ( class NodeSessionAbstractAction(AbstractAction, identifier="node_session_abstract"): """Base class for NodeSession actions.""" + config: "NodeSessionAbstractAction.ConfigSchema" + class ConfigSchema(AbstractAction.ConfigSchema): """Base configuration schema for NodeSessionAbstractActions.""" @@ -34,8 +36,7 @@ class NodeSessionAbstractAction(AbstractAction, identifier="node_session_abstrac class NodeSessionsRemoteLoginAction(NodeSessionAbstractAction, identifier="node_session_remote_login"): """Action which performs a remote session login.""" - username: str - password: str + config: "NodeSessionsRemoteLoginAction.ConfigSchema" class ConfigSchema(NodeSessionAbstractAction.ConfigSchema): """Configuration schema for NodeSessionsRemoteLoginAction.""" @@ -64,6 +65,8 @@ class NodeSessionsRemoteLoginAction(NodeSessionAbstractAction, identifier="node_ class NodeSessionsRemoteLogoutAction(NodeSessionAbstractAction, identifier="node_session_remote_logoff"): """Action which performs a remote session logout.""" + config: "NodeSessionsRemoteLogoutAction.ConfigSchema" + class ConfigSchema(NodeSessionAbstractAction.ConfigSchema): """Configuration schema for NodeSessionsRemoteLogoutAction.""" @@ -80,9 +83,7 @@ class NodeSessionsRemoteLogoutAction(NodeSessionAbstractAction, identifier="node class NodeAccountChangePasswordAction(NodeSessionAbstractAction, identifier="node_account_change_password"): """Action which changes the password for a user.""" - username: str - current_password: str - new_password: str + config: "NodeAccountChangePasswordAction.ConfigSchema" class ConfigSchema(NodeSessionAbstractAction.ConfigSchema): """Configuration schema for NodeAccountsChangePasswordAction.""" @@ -103,5 +104,5 @@ class NodeAccountChangePasswordAction(NodeSessionAbstractAction, identifier="nod "change_password", config.username, config.current_password, - cls.new_password, + config.new_password, ] diff --git a/tests/assets/configs/firewall_actions_network.yaml b/tests/assets/configs/firewall_actions_network.yaml index a2b75be5..4c3b5000 100644 --- a/tests/assets/configs/firewall_actions_network.yaml +++ b/tests/assets/configs/firewall_actions_network.yaml @@ -114,12 +114,12 @@ agents: position: 1 permission: PERMIT src_ip: 192.168.0.10 - dst_ip: ALL + dest_ip: ALL src_port: ALL dst_port: ALL protocol_name: ALL - src_wildcard: 0 - dst_wildcard: 0 + source_wildcard_id: 0 + dest_wildcard_id: 0 2: action: firewall_acl_remove_rule options: @@ -158,7 +158,7 @@ agents: position: 1 permission: DENY src_ip: 192.168.10.10 # dmz_server - dst_ip: 192.168.0.10 # client_1 + dest_ip: 192.168.0.10 # client_1 src_port: HTTP dst_port: HTTP protocol_name: UDP @@ -180,7 +180,7 @@ agents: position: 2 permission: DENY src_ip: 192.168.10.10 # dmz_server - dst_ip: 192.168.0.10 # client_1 + dest_ip: 192.168.0.10 # client_1 src_port: HTTP dst_port: HTTP protocol_name: TCP @@ -202,7 +202,7 @@ agents: position: 10 permission: DENY src_ip: 192.168.20.10 # external_computer - dst_ip: 192.168.10.10 # dmz + dest_ip: 192.168.10.10 # dmz src_port: POSTGRES_SERVER dst_port: POSTGRES_SERVER protocol_name: ICMP @@ -224,7 +224,7 @@ agents: position: 1 permission: DENY src_ip: 192.168.20.10 # external_computer - dst_ip: 192.168.0.10 # client_1 + dest_ip: 192.168.0.10 # client_1 src_port: NONE dst_port: NONE protocol_name: none diff --git a/tests/integration_tests/game_layer/test_actions.py b/tests/integration_tests/game_layer/test_actions.py index c4350e1f..a31f325a 100644 --- a/tests/integration_tests/game_layer/test_actions.py +++ b/tests/integration_tests/game_layer/test_actions.py @@ -56,7 +56,7 @@ def test_node_service_scan_integration(game_and_agent: Tuple[PrimaiteGame, Proxy assert svc.health_state_visible == SoftwareHealthState.UNUSED # 2: Scan and check that the visible state is now correct - action = ("node_service_scan", {"node_name": "server_1", "service_name": "DNSServer"}) + action = ("node_service_scan", {"type":"node_service_scan" ,"node_name": "server_1", "service_name": "DNSServer"}) agent.store_action(action) game.step() assert svc.health_state_actual == SoftwareHealthState.GOOD @@ -67,7 +67,7 @@ def test_node_service_scan_integration(game_and_agent: Tuple[PrimaiteGame, Proxy assert svc.health_state_visible == SoftwareHealthState.GOOD # 4: Scan and check that the visible state is now correct - action = ("node_service_scan", {"node_name": "server_1", "service_name": "DNSServer"}) + action = ("node_service_scan", {"type":"node_service_scan", "node_name": "server_1", "service_name": "DNSServer"}) agent.store_action(action) game.step() assert svc.health_state_actual == SoftwareHealthState.COMPROMISED From d3c52d0d7296ddd18f4f6fb7d84856a169577849 Mon Sep 17 00:00:00 2001 From: Charlie Crane Date: Tue, 19 Nov 2024 18:58:51 +0000 Subject: [PATCH 42/58] #2912 - Remove some debugging print statements and apply pre-commit lint changes --- src/primaite/game/agent/actions/abstract.py | 3 +-- src/primaite/game/agent/actions/acl.py | 5 ++--- src/primaite/game/agent/actions/config.py | 15 ++++++++++----- src/primaite/game/agent/actions/host_nic.py | 1 + src/primaite/game/agent/actions/manager.py | 3 +-- src/primaite/game/agent/actions/node.py | 2 +- .../integration_tests/game_layer/test_actions.py | 4 ++-- 7 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/primaite/game/agent/actions/abstract.py b/src/primaite/game/agent/actions/abstract.py index 5c0594fd..cd14ef6d 100644 --- a/src/primaite/game/agent/actions/abstract.py +++ b/src/primaite/game/agent/actions/abstract.py @@ -8,6 +8,7 @@ from pydantic import BaseModel, ConfigDict from primaite.interface.request import RequestFormat + class AbstractAction(BaseModel): """Base class for actions.""" @@ -37,6 +38,4 @@ class AbstractAction(BaseModel): """Create an action component from a config dictionary.""" if not config.get("type"): config.update({"type": cls.__name__}) - print("oooh") - print(config) return cls(config=cls.ConfigSchema(**config)) diff --git a/src/primaite/game/agent/actions/acl.py b/src/primaite/game/agent/actions/acl.py index 11269a7e..7ab49732 100644 --- a/src/primaite/game/agent/actions/acl.py +++ b/src/primaite/game/agent/actions/acl.py @@ -127,8 +127,7 @@ class FirewallACLAddRuleAction(ACLAbstractAction, identifier="firewall_acl_add_r if config.src_ip == 0: return ["do_nothing"] # invalid formulation if config.src_port == 0: - return ["do_nothing"] # invalid configuration. - + return ["do_nothing"] # invalid configuration. return [ "network", @@ -153,7 +152,7 @@ class FirewallACLAddRuleAction(ACLAbstractAction, identifier="firewall_acl_add_r class FirewallACLRemoveRuleAction(AbstractAction, identifier="firewall_acl_remove_rule"): """Action which removes a rule from a firewall port's ACL.""" - config:"FirewallACLRemoveRuleAction.ConfigSchema" + config: "FirewallACLRemoveRuleAction.ConfigSchema" class ConfigSchema(AbstractAction.ConfigSchema): """Configuration schema for FirewallACLRemoveRuleAction.""" diff --git a/src/primaite/game/agent/actions/config.py b/src/primaite/game/agent/actions/config.py index da9f77e6..7c72e57d 100644 --- a/src/primaite/game/agent/actions/config.py +++ b/src/primaite/game/agent/actions/config.py @@ -38,7 +38,15 @@ class ConfigureRansomwareScriptAction(AbstractAction, identifier="c2_server_rans """Return the action formatted as a request that can be ingested by the simulation.""" if config.node_name is None: return ["do_nothing"] - return ["network", "node", config.node_name, "application", "RansomwareScript", "configure", config.model_config] + return [ + "network", + "node", + config.node_name, + "application", + "RansomwareScript", + "configure", + config.model_config, + ] class ConfigureDoSBotAction(AbstractAction, identifier="configure_dos_bot"): @@ -207,10 +215,7 @@ class ExfiltrationC2ServerAction(AbstractAction, identifier="c2_server_data_exfi exfiltration_folder_name: Optional[str] @classmethod - def form_request( - cls, - config: ConfigSchema - ) -> RequestFormat: + def form_request(cls, config: ConfigSchema) -> RequestFormat: """Return the action formatted as a request that can be ingested by the simulation.""" if config.node_name is None: return ["do_nothing"] diff --git a/src/primaite/game/agent/actions/host_nic.py b/src/primaite/game/agent/actions/host_nic.py index 6df241bc..e2adf7d7 100644 --- a/src/primaite/game/agent/actions/host_nic.py +++ b/src/primaite/game/agent/actions/host_nic.py @@ -1,5 +1,6 @@ # © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import ClassVar + from primaite.game.agent.actions.manager import AbstractAction from primaite.interface.request import RequestFormat diff --git a/src/primaite/game/agent/actions/manager.py b/src/primaite/game/agent/actions/manager.py index 9ef94069..a413f6dc 100644 --- a/src/primaite/game/agent/actions/manager.py +++ b/src/primaite/game/agent/actions/manager.py @@ -31,7 +31,6 @@ class DoNothingAction(AbstractAction, identifier="do_nothing"): class ConfigSchema(AbstractAction.ConfigSchema): """Configuration Schema for DoNothingAction.""" - # type: Literal["do_nothing"] = "do_nothing" type: str = "do_nothing" @classmethod @@ -133,7 +132,7 @@ class ActionManager: def space(self) -> spaces.Space: """Return the gymnasium action space for this agent.""" return spaces.Discrete(len(self.action_map)) - + @classmethod def from_config(cls, game: "PrimaiteGame", cfg: Dict) -> "ActionManager": """ diff --git a/src/primaite/game/agent/actions/node.py b/src/primaite/game/agent/actions/node.py index 3c70d495..4ecc1393 100644 --- a/src/primaite/game/agent/actions/node.py +++ b/src/primaite/game/agent/actions/node.py @@ -90,7 +90,7 @@ class NodeNMAPAbstractAction(AbstractAction, identifier="node_nmap_abstract_acti """Base Configuration Schema for NodeNMAP actions.""" target_ip_address: Union[str, List[str]] - show: bool = False + show: bool = False node_name: str @classmethod diff --git a/tests/integration_tests/game_layer/test_actions.py b/tests/integration_tests/game_layer/test_actions.py index a31f325a..a21ad34f 100644 --- a/tests/integration_tests/game_layer/test_actions.py +++ b/tests/integration_tests/game_layer/test_actions.py @@ -56,7 +56,7 @@ def test_node_service_scan_integration(game_and_agent: Tuple[PrimaiteGame, Proxy assert svc.health_state_visible == SoftwareHealthState.UNUSED # 2: Scan and check that the visible state is now correct - action = ("node_service_scan", {"type":"node_service_scan" ,"node_name": "server_1", "service_name": "DNSServer"}) + action = ("node_service_scan", {"type": "node_service_scan", "node_name": "server_1", "service_name": "DNSServer"}) agent.store_action(action) game.step() assert svc.health_state_actual == SoftwareHealthState.GOOD @@ -67,7 +67,7 @@ def test_node_service_scan_integration(game_and_agent: Tuple[PrimaiteGame, Proxy assert svc.health_state_visible == SoftwareHealthState.GOOD # 4: Scan and check that the visible state is now correct - action = ("node_service_scan", {"type":"node_service_scan", "node_name": "server_1", "service_name": "DNSServer"}) + action = ("node_service_scan", {"type": "node_service_scan", "node_name": "server_1", "service_name": "DNSServer"}) agent.store_action(action) game.step() assert svc.health_state_actual == SoftwareHealthState.COMPROMISED From 8f610a3dd9b9d6497d0216e1201b8ee8bceaa92b Mon Sep 17 00:00:00 2001 From: Charlie Crane Date: Tue, 19 Nov 2024 19:39:23 +0000 Subject: [PATCH 43/58] #2912 - Minor changes to documentation page for extensible actions --- .../how_to_guides/extensible_actions.rst | 4 +++- src/primaite/game/agent/actions/acl.py | 19 ------------------- 2 files changed, 3 insertions(+), 20 deletions(-) diff --git a/docs/source/how_to_guides/extensible_actions.rst b/docs/source/how_to_guides/extensible_actions.rst index 576aa75f..6e44a905 100644 --- a/docs/source/how_to_guides/extensible_actions.rst +++ b/docs/source/how_to_guides/extensible_actions.rst @@ -27,6 +27,8 @@ New actions to be used within PrimAITE require: class ExampleAction(AbstractAction, identifier="Example_action"): """An example action for demonstration purposes.""" + config: "ExampleAction.ConfigSchema" + class ConfigSchema(AbstractAction.ConfigSchema): """The configuration schema with all attributes expected goes here.""" target_application: str @@ -55,7 +57,7 @@ New actions to be used within PrimAITE require: "node", config.node_name, "file_system", - cls.model_fields["verb"].default, + config.verb, "folder", config.folder_name, ] diff --git a/src/primaite/game/agent/actions/acl.py b/src/primaite/game/agent/actions/acl.py index 7ab49732..e8ad59f5 100644 --- a/src/primaite/game/agent/actions/acl.py +++ b/src/primaite/game/agent/actions/acl.py @@ -84,19 +84,6 @@ class FirewallACLAddRuleAction(ACLAbstractAction, identifier="firewall_acl_add_r config: "FirewallACLAddRuleAction.ConfigSchema" - # max_acl_rules: int - # num_ips: int - # num_ports: int - # num_protocols: int - # num_permissions: int = 3 - # permission: str - # target_firewall_nodename: str - # src_ip: str - # dst_ip: str - # dst_wildcard: str - # src_port: Union[int| None] - # dst_port: Union[int | None] - class ConfigSchema(ACLAbstractAction.ConfigSchema): """Configuration schema for FirewallACLAddRuleAction.""" @@ -113,12 +100,6 @@ class FirewallACLAddRuleAction(ACLAbstractAction, identifier="firewall_acl_add_r source_wildcard_id: int dest_wildcard_id: int - # max_acl_rules: int - # num_ips: int - # num_ports: int - # num_protocols: int - # num_permissions: int = 3 - @classmethod def form_request(cls, config: ConfigSchema) -> List[str]: """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" From be174b64774e10196bcc539536939f76fb78aa8f Mon Sep 17 00:00:00 2001 From: Charlie Crane Date: Fri, 6 Dec 2024 15:12:31 +0000 Subject: [PATCH 44/58] #2912 - Actioning review comments --- src/primaite/game/agent/actions/__init__.py | 1 + src/primaite/game/agent/actions/abstract.py | 2 - src/primaite/game/agent/actions/acl.py | 77 ++++++++++--------- .../game/agent/actions/application.py | 6 -- src/primaite/game/agent/actions/config.py | 17 +--- src/primaite/game/agent/actions/manager.py | 51 ++---------- 6 files changed, 53 insertions(+), 101 deletions(-) diff --git a/src/primaite/game/agent/actions/__init__.py b/src/primaite/game/agent/actions/__init__.py index 7f054591..016a09ba 100644 --- a/src/primaite/game/agent/actions/__init__.py +++ b/src/primaite/game/agent/actions/__init__.py @@ -29,4 +29,5 @@ __all__ = ( "node", "service", "session", + "ActionManager", ) diff --git a/src/primaite/game/agent/actions/abstract.py b/src/primaite/game/agent/actions/abstract.py index cd14ef6d..ef22ec54 100644 --- a/src/primaite/game/agent/actions/abstract.py +++ b/src/primaite/game/agent/actions/abstract.py @@ -36,6 +36,4 @@ class AbstractAction(BaseModel): @classmethod def from_config(cls, config: Dict) -> "AbstractAction": """Create an action component from a config dictionary.""" - if not config.get("type"): - config.update({"type": cls.__name__}) return cls(config=cls.ConfigSchema(**config)) diff --git a/src/primaite/game/agent/actions/acl.py b/src/primaite/game/agent/actions/acl.py index e8ad59f5..fb18d025 100644 --- a/src/primaite/game/agent/actions/acl.py +++ b/src/primaite/game/agent/actions/acl.py @@ -1,5 +1,7 @@ # © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK -from typing import List, Literal, Union +from __future__ import annotations + +from typing import List from primaite.game.agent.actions.manager import AbstractAction from primaite.interface.request import RequestFormat @@ -12,34 +14,48 @@ __all__ = ( ) -class ACLAbstractAction(AbstractAction, identifier="acl_abstract_action"): - """Base class for ACL actions.""" +class ACLAddRuleAbstractAction(AbstractAction, identifier="acl_add_rule_abstract_action"): + """Base abstract class for ACL add rule actions.""" + + config: ConfigSchema = "ACLAddRuleAbstractAction.ConfigSchema" class ConfigSchema(AbstractAction.ConfigSchema): - """Configuration Schema base for ACL abstract actions.""" + """Configuration Schema base for ACL add rule abstract actions.""" src_ip: str protocol_name: str + permission: str + position: int + src_ip: str + dst_ip: str + src_port: str + dst_port: str + src_wildcard: int + dst_wildcard: int -class RouterACLAddRuleAction(AbstractAction, identifier="router_acl_add_rule"): +class ACLRemoveRuleAbstractAction(AbstractAction, identifier="acl_remove_rule_abstract_action"): + """Base abstract class for ACL remove rule actions.""" + + config: ConfigSchema = "ACLRemoveRuleAbstractAction.ConfigSchema" + + class ConfigSchema(AbstractAction.ConfigSchema): + """Configuration Schema base for ACL remove rule abstract actions.""" + + src_ip: str + protocol_name: str + position: int + + +class RouterACLAddRuleAction(ACLAddRuleAbstractAction, identifier="router_acl_add_rule"): """Action which adds a rule to a router's ACL.""" config: "RouterACLAddRuleAction.ConfigSchema" - class ConfigSchema(AbstractAction.ConfigSchema): + class ConfigSchema(ACLAddRuleAbstractAction.ConfigSchema): """Configuration Schema for RouterACLAddRuleAction.""" target_router: str - permission: str - protocol_name: str - position: int - src_ip: str - src_wildcard: int - source_port: str - dst_ip: str - dst_wildcard: int - dst_port: str @classmethod def form_request(cls, config: ConfigSchema) -> List[str]: @@ -62,16 +78,15 @@ class RouterACLAddRuleAction(AbstractAction, identifier="router_acl_add_rule"): ] -class RouterACLRemoveRuleAction(AbstractAction, identifier="router_acl_remove_rule"): +class RouterACLRemoveRuleAction(ACLRemoveRuleAbstractAction, identifier="router_acl_remove_rule"): """Action which removes a rule from a router's ACL.""" config: "RouterACLRemoveRuleAction.ConfigSchema" - class ConfigSchema(AbstractAction.ConfigSchema): + class ConfigSchema(ACLRemoveRuleAbstractAction.ConfigSchema): """Configuration schema for RouterACLRemoveRuleAction.""" target_router: str - position: int @classmethod def form_request(cls, config: ConfigSchema) -> RequestFormat: @@ -79,31 +94,22 @@ class RouterACLRemoveRuleAction(AbstractAction, identifier="router_acl_remove_ru return ["network", "node", config.target_router, "acl", "remove_rule", config.position] -class FirewallACLAddRuleAction(ACLAbstractAction, identifier="firewall_acl_add_rule"): +class FirewallACLAddRuleAction(ACLAddRuleAbstractAction, identifier="firewall_acl_add_rule"): """Action which adds a rule to a firewall port's ACL.""" config: "FirewallACLAddRuleAction.ConfigSchema" - class ConfigSchema(ACLAbstractAction.ConfigSchema): + class ConfigSchema(ACLAddRuleAbstractAction.ConfigSchema): """Configuration schema for FirewallACLAddRuleAction.""" target_firewall_nodename: str firewall_port_name: str firewall_port_direction: str - position: int - permission: str - src_ip: str - dest_ip: str - src_port: str - dst_port: str - protocol_name: str - source_wildcard_id: int - dest_wildcard_id: int @classmethod def form_request(cls, config: ConfigSchema) -> List[str]: """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - if config.protocol_name == None: + if config.protocol_name is None: return ["do_nothing"] # NOT SUPPORTED, JUST DO NOTHING IF WE COME ACROSS THIS if config.src_ip == 0: return ["do_nothing"] # invalid formulation @@ -121,27 +127,26 @@ class FirewallACLAddRuleAction(ACLAbstractAction, identifier="firewall_acl_add_r config.permission, config.protocol_name, config.src_ip, - config.source_wildcard_id, + config.src_wildcard, config.src_port, - config.dest_ip, - config.dest_wildcard_id, + config.dst_ip, + config.dst_wildcard, config.dst_port, config.position, ] -class FirewallACLRemoveRuleAction(AbstractAction, identifier="firewall_acl_remove_rule"): +class FirewallACLRemoveRuleAction(ACLRemoveRuleAbstractAction, identifier="firewall_acl_remove_rule"): """Action which removes a rule from a firewall port's ACL.""" config: "FirewallACLRemoveRuleAction.ConfigSchema" - class ConfigSchema(AbstractAction.ConfigSchema): + class ConfigSchema(ACLRemoveRuleAbstractAction.ConfigSchema): """Configuration schema for FirewallACLRemoveRuleAction.""" target_firewall_nodename: str firewall_port_name: str firewall_port_direction: str - position: int @classmethod def form_request(cls, config: ConfigSchema) -> List[str]: diff --git a/src/primaite/game/agent/actions/application.py b/src/primaite/game/agent/actions/application.py index f515a8ec..91e34eae 100644 --- a/src/primaite/game/agent/actions/application.py +++ b/src/primaite/game/agent/actions/application.py @@ -34,8 +34,6 @@ class NodeApplicationAbstractAction(AbstractAction, identifier="node_application @classmethod def form_request(cls, config: ConfigSchema) -> RequestFormat: """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - if config.node_name is None or config.application_name is None: - return ["do_nothing"] return [ "network", "node", @@ -103,8 +101,6 @@ class NodeApplicationInstallAction(NodeApplicationAbstractAction, identifier="no @classmethod def form_request(cls, config: ConfigSchema) -> RequestFormat: """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - if config.node_name is None: - return ["do_nothing"] return [ "network", "node", @@ -129,8 +125,6 @@ class NodeApplicationRemoveAction(NodeApplicationAbstractAction, identifier="nod @classmethod def form_request(cls, config: ConfigSchema) -> RequestFormat: """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - if config.node_name is None: - return ["do_nothing"] return [ "network", "node", diff --git a/src/primaite/game/agent/actions/config.py b/src/primaite/game/agent/actions/config.py index 7c72e57d..319cd212 100644 --- a/src/primaite/game/agent/actions/config.py +++ b/src/primaite/game/agent/actions/config.py @@ -1,8 +1,8 @@ # © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK -from typing import Dict, List, Optional, Union +from typing import List, Optional, Union -from pydantic import BaseModel, ConfigDict, Field, field_validator, ValidationInfo +from pydantic import ConfigDict, Field, field_validator, ValidationInfo from primaite.game.agent.actions.manager import AbstractAction, ActionManager from primaite.interface.request import RequestFormat @@ -27,7 +27,6 @@ class ConfigureRansomwareScriptAction(AbstractAction, identifier="c2_server_rans class ConfigSchema(AbstractAction.ConfigSchema): """Configuration schema for ConfigureRansomwareScriptAction.""" - model_config = ConfigDict(extra="forbid") node_name: str server_ip_address: Optional[str] server_password: Optional[str] @@ -109,17 +108,7 @@ class ConfigureC2BeaconAction(AbstractAction, identifier="configure_c2_beacon"): @classmethod def form_request(self, config: ConfigSchema) -> RequestFormat: """Return the action formatted as a request that can be ingested by the simulation.""" - if config.node_name is None: - return ["do_nothing"] - configuration = ConfigureC2BeaconAction.ConfigSchema( - c2_server_ip_address=config.c2_server_ip_address, - keep_alive_frequency=config.keep_alive_frequency, - masquerade_port=config.masquerade_port, - masquerade_protocol=config.masquerade_protocol, - ) - - ConfigureC2BeaconAction.ConfigSchema.model_validate(configuration) # check that options adhere to schema - + configuration = [] return ["network", "node", config.node_name, "application", "C2Beacon", "configure", configuration] diff --git a/src/primaite/game/agent/actions/manager.py b/src/primaite/game/agent/actions/manager.py index a413f6dc..b89704f4 100644 --- a/src/primaite/game/agent/actions/manager.py +++ b/src/primaite/game/agent/actions/manager.py @@ -13,8 +13,7 @@ agents: from __future__ import annotations -import itertools -from typing import Dict, List, Literal, Optional, Tuple +from typing import Dict, List, Optional, Tuple from gymnasium import spaces @@ -45,7 +44,9 @@ class ActionManager: def __init__( self, actions: List[Dict], # stores list of actions available to agent - act_map: Optional[Dict[int, Dict]] = None, # allows restricting set of possible actions + act_map: Optional[ + Dict[int, Dict] + ] = None, # allows restricting set of possible actions - TODO: Refactor to be a list? *args, **kwargs, ) -> None: @@ -79,43 +80,6 @@ class ActionManager: # make sure all numbers between 0 and N are represented as dict keys in action map assert all([i in self.action_map.keys() for i in range(len(self.action_map))]) - def _enumerate_actions( - self, - ) -> Dict[int, Tuple[str, Dict]]: - """Generate a list of all the possible actions that could be taken. - - This enumerates all actions all combinations of parameters you could choose for those actions. The output - of this function is intended to populate the self.action_map parameter in the situation where the user provides - a list of action types, but doesn't specify any subset of actions that should be made available to the agent. - - The enumeration relies on the Actions' `shape` attribute. - - :return: An action map maps consecutive integers to a combination of Action type and parameter choices. - An example output could be: - {0: ("do_nothing", {'dummy': 0}), - 1: ("node_os_scan", {'node_name': computer}), - 2: ("node_os_scan", {'node_name': server}), - 3: ("node_folder_scan", {'node_name:computer, folder_name:downloads}), - ... #etc... - } - :rtype: Dict[int, Tuple[AbstractAction, Dict]] - """ - all_action_possibilities = [] - for act_name, action in self.actions.items(): - param_names = list(action.shape.keys()) - num_possibilities = list(action.shape.values()) - possibilities = [range(n) for n in num_possibilities] - - param_combinations = list(itertools.product(*possibilities)) - all_action_possibilities.extend( - [ - (act_name, {param_names[i]: param_combinations[j][i] for i in range(len(param_names))}) - for j in range(len(param_combinations)) - ] - ) - - return {i: p for i, p in enumerate(all_action_possibilities)} - def get_action(self, action: int) -> Tuple[str, Dict]: """Produce action in CAOS format.""" """the agent chooses an action (as an integer), this is converted into an action in CAOS format""" @@ -125,8 +89,9 @@ class ActionManager: def form_request(self, action_identifier: str, action_options: Dict) -> RequestFormat: """Take action in CAOS format and use the execution definition to change it into PrimAITE request format.""" - act_obj = self.actions[action_identifier].from_config(config=action_options) - return act_obj.form_request(config=act_obj.config) + act_class = AbstractAction._registry[action_identifier] + config = act_class.ConfigSchema(**action_options) + return act_class.form_request(config=config) @property def space(self) -> spaces.Space: @@ -134,7 +99,7 @@ class ActionManager: return spaces.Discrete(len(self.action_map)) @classmethod - def from_config(cls, game: "PrimaiteGame", cfg: Dict) -> "ActionManager": + def from_config(cls, game: "PrimaiteGame", cfg: Dict) -> "ActionManager": # noqa: F821 """ Construct an ActionManager from a config definition. From a8fbb002e4299d6a34f3801a56b55b61f7fc674c Mon Sep 17 00:00:00 2001 From: Charlie Crane Date: Mon, 9 Dec 2024 09:54:35 +0000 Subject: [PATCH 45/58] #2912 - Updates following review, ACL rules now have validation for ConfigSchema fields --- src/primaite/game/agent/actions/acl.py | 42 ++++++++++++++++++----- src/primaite/game/agent/actions/config.py | 3 +- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/src/primaite/game/agent/actions/acl.py b/src/primaite/game/agent/actions/acl.py index fb18d025..f129a82f 100644 --- a/src/primaite/game/agent/actions/acl.py +++ b/src/primaite/game/agent/actions/acl.py @@ -3,8 +3,12 @@ from __future__ import annotations from typing import List +from pydantic import field_validator + from primaite.game.agent.actions.manager import AbstractAction from primaite.interface.request import RequestFormat +from primaite.utils.validation.ip_protocol import is_valid_protocol, protocol_validator +from primaite.utils.validation.port import is_valid_port __all__ = ( "RouterACLAddRuleAction", @@ -33,6 +37,35 @@ class ACLAddRuleAbstractAction(AbstractAction, identifier="acl_add_rule_abstract src_wildcard: int dst_wildcard: int + @field_validator( + src_port, + dst_port, + mode="before", + ) + @classmethod + def valid_port(cls, v: str) -> int: + """Check that inputs are valid.""" + return is_valid_port(v) + + @field_validator( + src_ip, + dst_ip, + mode="before", + ) + @classmethod + def valid_ip(cls, v: str) -> str: + """Check that a valid IP has been provided for src and dst.""" + return is_valid_protocol(v) + + @field_validator( + protocol_name, + mode="before", + ) + @classmethod + def is_valid_protocol(cls, v: str) -> bool: + """Check that we are using a valid protocol.""" + return protocol_validator(v) + class ACLRemoveRuleAbstractAction(AbstractAction, identifier="acl_remove_rule_abstract_action"): """Base abstract class for ACL remove rule actions.""" @@ -70,7 +103,7 @@ class RouterACLAddRuleAction(ACLAddRuleAbstractAction, identifier="router_acl_ad config.protocol_name, config.src_ip, config.src_wildcard, - config.source_port, + config.src_port, config.dst_ip, config.dst_wildcard, config.dst_port, @@ -109,13 +142,6 @@ class FirewallACLAddRuleAction(ACLAddRuleAbstractAction, identifier="firewall_ac @classmethod def form_request(cls, config: ConfigSchema) -> List[str]: """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - if config.protocol_name is None: - return ["do_nothing"] # NOT SUPPORTED, JUST DO NOTHING IF WE COME ACROSS THIS - if config.src_ip == 0: - return ["do_nothing"] # invalid formulation - if config.src_port == 0: - return ["do_nothing"] # invalid configuration. - return [ "network", "node", diff --git a/src/primaite/game/agent/actions/config.py b/src/primaite/game/agent/actions/config.py index 319cd212..050e9b94 100644 --- a/src/primaite/game/agent/actions/config.py +++ b/src/primaite/game/agent/actions/config.py @@ -108,8 +108,7 @@ class ConfigureC2BeaconAction(AbstractAction, identifier="configure_c2_beacon"): @classmethod def form_request(self, config: ConfigSchema) -> RequestFormat: """Return the action formatted as a request that can be ingested by the simulation.""" - configuration = [] - return ["network", "node", config.node_name, "application", "C2Beacon", "configure", configuration] + return ["network", "node", config.node_name, "application", "C2Beacon", "configure", config] class NodeSendRemoteCommandAction(AbstractAction, identifier="node_send_remote_command"): From 386717fa4166d935e09524681905b285d56e3223 Mon Sep 17 00:00:00 2001 From: Charlie Crane Date: Mon, 9 Dec 2024 09:59:47 +0000 Subject: [PATCH 46/58] #2912 - removal of the rom_config method as this shouldn't be needed for the actions refactor --- src/primaite/game/agent/actions/abstract.py | 5 ----- src/primaite/game/agent/actions/acl.py | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/primaite/game/agent/actions/abstract.py b/src/primaite/game/agent/actions/abstract.py index ef22ec54..8c332d5e 100644 --- a/src/primaite/game/agent/actions/abstract.py +++ b/src/primaite/game/agent/actions/abstract.py @@ -32,8 +32,3 @@ class AbstractAction(BaseModel): def form_request(cls, config: ConfigSchema) -> RequestFormat: """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" pass - - @classmethod - def from_config(cls, config: Dict) -> "AbstractAction": - """Create an action component from a config dictionary.""" - return cls(config=cls.ConfigSchema(**config)) diff --git a/src/primaite/game/agent/actions/acl.py b/src/primaite/game/agent/actions/acl.py index f129a82f..5cd7a355 100644 --- a/src/primaite/game/agent/actions/acl.py +++ b/src/primaite/game/agent/actions/acl.py @@ -79,6 +79,24 @@ class ACLRemoveRuleAbstractAction(AbstractAction, identifier="acl_remove_rule_ab protocol_name: str position: int + @field_validator( + src_ip, + mode="before", + ) + @classmethod + def valid_ip(cls, v: str) -> str: + """Check that a valid IP has been provided for src and dst.""" + return is_valid_protocol(v) + + @field_validator( + protocol_name, + mode="before", + ) + @classmethod + def is_valid_protocol(cls, v: str) -> bool: + """Check that we are using a valid protocol.""" + return protocol_validator(v) + class RouterACLAddRuleAction(ACLAddRuleAbstractAction, identifier="router_acl_add_rule"): """Action which adds a rule to a router's ACL.""" From 068ad2f1fa17cac80856bd71fa4d00f5b673b89c Mon Sep 17 00:00:00 2001 From: Charlie Crane Date: Mon, 9 Dec 2024 13:56:40 +0000 Subject: [PATCH 47/58] #2912 - Updates to get tests to pass. Some ACL rules still misbehaving --- src/primaite/game/agent/actions/acl.py | 35 +++++------ .../configs/firewall_actions_network.yaml | 17 +++--- .../game_layer/test_actions.py | 58 +++++++++++++++---- 3 files changed, 75 insertions(+), 35 deletions(-) diff --git a/src/primaite/game/agent/actions/acl.py b/src/primaite/game/agent/actions/acl.py index 5cd7a355..37dde757 100644 --- a/src/primaite/game/agent/actions/acl.py +++ b/src/primaite/game/agent/actions/acl.py @@ -1,14 +1,16 @@ # © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from __future__ import annotations +from ipaddress import IPv4Address from typing import List from pydantic import field_validator from primaite.game.agent.actions.manager import AbstractAction from primaite.interface.request import RequestFormat -from primaite.utils.validation.ip_protocol import is_valid_protocol, protocol_validator -from primaite.utils.validation.port import is_valid_port +from primaite.utils.validation.ip_protocol import protocol_validator +from primaite.utils.validation.ipv4_address import ipv4_validator +from primaite.utils.validation.port import port_validator __all__ = ( "RouterACLAddRuleAction", @@ -26,39 +28,38 @@ class ACLAddRuleAbstractAction(AbstractAction, identifier="acl_add_rule_abstract class ConfigSchema(AbstractAction.ConfigSchema): """Configuration Schema base for ACL add rule abstract actions.""" - src_ip: str + src_ip: IPv4Address protocol_name: str permission: str position: int - src_ip: str - dst_ip: str - src_port: str - dst_port: str + dst_ip: IPv4Address + src_port: int + dst_port: int src_wildcard: int dst_wildcard: int @field_validator( - src_port, - dst_port, + "src_port", + "dst_port", mode="before", ) @classmethod def valid_port(cls, v: str) -> int: """Check that inputs are valid.""" - return is_valid_port(v) + return port_validator(v) @field_validator( - src_ip, - dst_ip, + "src_ip", + "dst_ip", mode="before", ) @classmethod def valid_ip(cls, v: str) -> str: """Check that a valid IP has been provided for src and dst.""" - return is_valid_protocol(v) + return ipv4_validator(v) @field_validator( - protocol_name, + "protocol_name", mode="before", ) @classmethod @@ -80,16 +81,16 @@ class ACLRemoveRuleAbstractAction(AbstractAction, identifier="acl_remove_rule_ab position: int @field_validator( - src_ip, + "src_ip", mode="before", ) @classmethod def valid_ip(cls, v: str) -> str: """Check that a valid IP has been provided for src and dst.""" - return is_valid_protocol(v) + return ipv4_validator(v) @field_validator( - protocol_name, + "protocol_name", mode="before", ) @classmethod diff --git a/tests/assets/configs/firewall_actions_network.yaml b/tests/assets/configs/firewall_actions_network.yaml index 4c3b5000..29af3b55 100644 --- a/tests/assets/configs/firewall_actions_network.yaml +++ b/tests/assets/configs/firewall_actions_network.yaml @@ -108,18 +108,19 @@ agents: 1: action: firewall_acl_add_rule options: + type: firewall_acl_add_rule target_firewall_nodename: firewall firewall_port_name: internal firewall_port_direction: inbound position: 1 permission: PERMIT src_ip: 192.168.0.10 - dest_ip: ALL - src_port: ALL - dst_port: ALL - protocol_name: ALL - source_wildcard_id: 0 - dest_wildcard_id: 0 + dst_ip: 0.0.0.0 + src_port: 80 + dst_port: HTTP + protocol_name: TCP + src_wildcard: 0 + dst_wildcard: 0 2: action: firewall_acl_remove_rule options: @@ -136,7 +137,7 @@ agents: position: 1 permission: DENY src_ip: 192.168.0.10 # client 1 - dest_ip: ALL # ALL + dest_ip: ALL src_port: ARP dst_port: DNS protocol_name: ICMP @@ -240,11 +241,13 @@ agents: 13: action: network_port_disable options: + type: network_port_disable target_nodename: firewall port_id: 3 14: action: network_port_enable options: + type: network_port_enable target_nodename: firewall port_id: 3 options: diff --git a/tests/integration_tests/game_layer/test_actions.py b/tests/integration_tests/game_layer/test_actions.py index a21ad34f..9fdf029b 100644 --- a/tests/integration_tests/game_layer/test_actions.py +++ b/tests/integration_tests/game_layer/test_actions.py @@ -88,7 +88,7 @@ def test_node_service_fix_integration(game_and_agent: Tuple[PrimaiteGame, ProxyA svc.health_state_actual = SoftwareHealthState.COMPROMISED # 2: Apply a patch action - action = ("node_service_fix", {"node_name": "server_1", "service_name": "DNSServer"}) + action = ("node_service_fix", {"type": "node_service_fix", "node_name": "server_1", "service_name": "DNSServer"}) agent.store_action(action) game.step() @@ -123,16 +123,17 @@ def test_router_acl_addrule_integration(game_and_agent: Tuple[PrimaiteGame, Prox action = ( "router_acl_add_rule", { + "type": "router_acl_add_rule", "target_router": "router", "position": 4, "permission": "DENY", "src_ip": "10.0.1.2", "src_wildcard": 0, - "source_port": "ALL", + "src_port": "HTTP", "dst_ip": "10.0.2.3", "dst_wildcard": 0, - "dst_port": "ALL", - "protocol_name": "ALL", + "dst_port": "HTTP", + "protocol_name": "udp", }, ) agent.store_action(action) @@ -150,6 +151,7 @@ def test_router_acl_addrule_integration(game_and_agent: Tuple[PrimaiteGame, Prox action = ( "router_acl_add_rule", { + "type": "router_acl_add_rule", "target_router": "router", "position": 5, # 5th rule "permission": "DENY", # DENY @@ -190,6 +192,7 @@ def test_router_acl_removerule_integration(game_and_agent: Tuple[PrimaiteGame, P action = ( "router_acl_remove_rule", { + "type": "router_acl_remove_rule", "target_router": "router", "position": 3, # 4th rule }, @@ -223,6 +226,7 @@ def test_host_nic_disable_integration(game_and_agent: Tuple[PrimaiteGame, ProxyA action = ( "host_nic_disable", { + "type": "host_nic_disable", "node_name": "client_1", # client_1 "nic_num": 1, # the only nic (eth-1) }, @@ -254,6 +258,7 @@ def test_host_nic_enable_integration(game_and_agent: Tuple[PrimaiteGame, ProxyAg action = ( "host_nic_enable", { + "type": "host_nic_enable", "node_name": "client_1", # client_1 "nic_num": 1, # the only nic (eth-1) }, @@ -281,6 +286,7 @@ def test_node_file_scan_integration(game_and_agent: Tuple[PrimaiteGame, ProxyAge action = ( "node_file_scan", { + "type": "node_file_scan", "node_name": "client_1", # client_1, "folder_name": "downloads", # downloads, "file_name": "cat.png", # cat.png @@ -318,6 +324,7 @@ def test_node_file_delete_integration(game_and_agent: Tuple[PrimaiteGame, ProxyA action = ( "node_file_delete", { + "type": "node_file_delete", "node_name": "client_1", # client_1 "folder_name": "downloads", # downloads "file_name": "cat.png", # cat.png @@ -340,7 +347,13 @@ def test_node_file_create(game_and_agent: Tuple[PrimaiteGame, ProxyAgent]): action = ( "node_file_create", - {"node_name": "client_1", "folder_name": "test", "file_name": "file.txt", "force": "False"}, + { + "type": "node_file_create", + "node_name": "client_1", + "folder_name": "test", + "file_name": "file.txt", + "force": "False", + }, ) agent.store_action(action) game.step() @@ -357,6 +370,7 @@ def test_node_file_access(game_and_agent: Tuple[PrimaiteGame, ProxyAgent]): action = ( "node_file_create", { + "type": "node_file_create", "node_name": "client_1", "folder_name": "test", "file_name": "file.txt", @@ -370,6 +384,7 @@ def test_node_file_access(game_and_agent: Tuple[PrimaiteGame, ProxyAgent]): action = ( "node_file_access", { + "type": "node_file_access", "node_name": "client_1", "folder_name": "test", "file_name": "file.txt", @@ -390,6 +405,7 @@ def test_node_folder_create(game_and_agent: Tuple[PrimaiteGame, ProxyAgent]): action = ( "node_folder_create", { + "type": "node_folder_create", "node_name": "client_1", "folder_name": "test", }, @@ -418,6 +434,7 @@ def test_network_router_port_disable_integration(game_and_agent: Tuple[PrimaiteG action = ( "network_port_disable", { + "type": "network_port_disable", "target_nodename": "router", # router "port_id": 1, # port 1 }, @@ -450,6 +467,7 @@ def test_network_router_port_enable_integration(game_and_agent: Tuple[PrimaiteGa action = ( "network_port_enable", { + "type": "network_port_enable", "target_nodename": "router", # router "port_id": 1, # port 1 }, @@ -478,7 +496,10 @@ def test_node_application_scan_integration(game_and_agent: Tuple[PrimaiteGame, P assert browser.health_state_visible == SoftwareHealthState.UNUSED # 2: Scan and check that the visible state is now correct - action = ("node_application_scan", {"node_name": "client_1", "application_name": "WebBrowser"}) + action = ( + "node_application_scan", + {"type": "node_application_scan", "node_name": "client_1", "application_name": "WebBrowser"}, + ) agent.store_action(action) game.step() assert browser.health_state_actual == SoftwareHealthState.GOOD @@ -489,7 +510,10 @@ def test_node_application_scan_integration(game_and_agent: Tuple[PrimaiteGame, P assert browser.health_state_visible == SoftwareHealthState.GOOD # 4: Scan and check that the visible state is now correct - action = ("node_application_scan", {"node_name": "client_1", "application_name": "WebBrowser"}) + action = ( + "node_application_scan", + {"type": "node_application_scan", "node_name": "client_1", "application_name": "WebBrowser"}, + ) agent.store_action(action) game.step() assert browser.health_state_actual == SoftwareHealthState.COMPROMISED @@ -510,7 +534,10 @@ def test_node_application_fix_integration(game_and_agent: Tuple[PrimaiteGame, Pr browser.health_state_actual = SoftwareHealthState.COMPROMISED # 2: Apply a fix action - action = ("node_application_fix", {"node_name": "client_1", "application_name": "WebBrowser"}) + action = ( + "node_application_fix", + {"type": "node_application_fix", "node_name": "client_1", "application_name": "WebBrowser"}, + ) agent.store_action(action) game.step() @@ -536,7 +563,10 @@ def test_node_application_close_integration(game_and_agent: Tuple[PrimaiteGame, assert browser.operating_state == ApplicationOperatingState.RUNNING # 2: Apply a close action - action = ("node_application_close", {"node_name": "client_1", "application_name": "WebBrowser"}) + action = ( + "node_application_close", + {"type": "node_application_close", "node_name": "client_1", "application_name": "WebBrowser"}, + ) agent.store_action(action) game.step() @@ -555,13 +585,19 @@ def test_node_application_install_and_uninstall_integration(game_and_agent: Tupl assert client_1.software_manager.software.get("DoSBot") is None - action = ("node_application_install", {"node_name": "client_1", "application_name": "DoSBot"}) + action = ( + "node_application_install", + {"type": "node_application_install", "node_name": "client_1", "application_name": "DoSBot"}, + ) agent.store_action(action) game.step() assert client_1.software_manager.software.get("DoSBot") is not None - action = ("node_application_remove", {"node_name": "client_1", "application_name": "DoSBot"}) + action = ( + "node_application_remove", + {"type": "node_application_remove", "node_name": "client_1", "application_name": "DoSBot"}, + ) agent.store_action(action) game.step() From e40fd053f73ccd78c66ec55ceb43a5c309e98075 Mon Sep 17 00:00:00 2001 From: Charlie Crane Date: Wed, 11 Dec 2024 10:32:15 +0000 Subject: [PATCH 48/58] #2912 - Removing print statements left in from debugging --- .../simulator/system/core/software_manager.py | 4 ++++ tests/assets/configs/data_manipulation.yaml | 14 +++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/primaite/simulator/system/core/software_manager.py b/src/primaite/simulator/system/core/software_manager.py index 04a3e3fb..2f19a8b0 100644 --- a/src/primaite/simulator/system/core/software_manager.py +++ b/src/primaite/simulator/system/core/software_manager.py @@ -207,6 +207,10 @@ class SoftwareManager: :param session_id: The Session ID from which the payload originates. Optional. :return: True if the payload was successfully sent, False otherwise. """ + print(payload) + print(dest_ip_address) + print(src_port) + print(session_id) return self.session_manager.receive_payload_from_software_manager( payload=payload, dst_ip_address=dest_ip_address, diff --git a/tests/assets/configs/data_manipulation.yaml b/tests/assets/configs/data_manipulation.yaml index 97442903..96751cad 100644 --- a/tests/assets/configs/data_manipulation.yaml +++ b/tests/assets/configs/data_manipulation.yaml @@ -33,7 +33,7 @@ agents: observation_space: null action_space: action_list: - - type: DONOTHING + - type: do_nothing - type: NODE_APPLICATION_EXECUTE options: nodes: @@ -47,7 +47,7 @@ agents: max_applications_per_node: 2 action_map: 0: - action: DONOTHING + action: do_nothing options: {} 1: action: NODE_APPLICATION_EXECUTE @@ -82,7 +82,7 @@ agents: observation_space: null action_space: action_list: - - type: DONOTHING + - type: do_nothing - type: NODE_APPLICATION_EXECUTE options: nodes: @@ -96,7 +96,7 @@ agents: max_applications_per_node: 2 action_map: 0: - action: DONOTHING + action: do_nothing options: {} 1: action: NODE_APPLICATION_EXECUTE @@ -132,7 +132,7 @@ agents: action_space: action_list: - - type: DONOTHING + - type: do_nothing - type: NODE_APPLICATION_EXECUTE options: nodes: @@ -236,7 +236,7 @@ agents: action_space: action_list: - - type: DONOTHING + - type: do_nothing - type: NODE_SERVICE_SCAN - type: NODE_SERVICE_STOP - type: NODE_SERVICE_START @@ -266,7 +266,7 @@ agents: action_map: 0: - action: DONOTHING + action: do_nothing options: {} # scan webapp service 1: From 47ed585ee2e4cbc2acbb962bf57fca8f9a31257b Mon Sep 17 00:00:00 2001 From: Charlie Crane Date: Thu, 12 Dec 2024 16:08:11 +0000 Subject: [PATCH 49/58] #2912 - Replace DONOTHING reference with do_nothing, tweaks following milpac actions --- src/primaite/_legacy/actions.py | 20 +++++++++++-------- src/primaite/game/agent/actions/manager.py | 5 ++++- src/primaite/game/agent/actions/node.py | 3 ++- src/primaite/game/agent/rewards.py | 8 ++++---- .../scripted_agents/data_manipulation_bot.py | 2 +- .../agent/scripted_agents/random_agent.py | 2 +- .../game/agent/scripted_agents/tap001.py | 2 +- .../Data-Manipulation-E2E-Demonstration.ipynb | 14 ++++++------- .../actions/test_c2_suite_actions.py | 2 +- .../actions/test_node_request_permission.py | 6 +++--- .../game_layer/test_RNG_seed.py | 8 ++++---- .../game_layer/test_action_mask.py | 2 +- .../game_layer/test_actions.py | 2 +- .../game_layer/test_rewards.py | 10 +++++----- .../_primaite/_game/_agent/test_actions.py | 6 +++--- .../_game/_agent/test_sticky_rewards.py | 16 +++++++-------- 16 files changed, 58 insertions(+), 50 deletions(-) diff --git a/src/primaite/_legacy/actions.py b/src/primaite/_legacy/actions.py index 64cbe0cf..0eda7d86 100644 --- a/src/primaite/_legacy/actions.py +++ b/src/primaite/_legacy/actions.py @@ -455,11 +455,12 @@ class NodeAbstractAction(AbstractAction): Any action which applies to a node and uses node_id as its only parameter can inherit from this base class. """ - @abstractmethod - def __init__(self, manager: "ActionManager", num_nodes: int, **kwargs) -> None: - super().__init__(manager=manager) - self.shape: Dict[str, int] = {"node_id": num_nodes} - self.verb: str # define but don't initialise: defends against children classes not defining this + config: "NodeAbstractAction.ConfigSchema" + + class ConfigSchema(AbstractAction.ConfigSchema): + """Configuration schema for NodeAbstractAction.""" + + verb: str = "Node_Abstract_Action" def form_request(self, node_id: int) -> RequestFormat: """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" @@ -478,9 +479,12 @@ class NodeOSScanAction(NodeAbstractAction): class NodeShutdownAction(NodeAbstractAction): """Action which shuts down a node.""" - def __init__(self, manager: "ActionManager", num_nodes: int, **kwargs) -> None: - super().__init__(manager=manager, num_nodes=num_nodes) - self.verb: str = "shutdown" + config: "NodeShutdownAction.ConfigSchema" + + class ConfigSchema(NodeAbstractAction.ConfigSchema): + """Configuration Schema for NodeShutdownAction.""" + + verb: str = "shutdown" class NodeStartupAction(NodeAbstractAction): diff --git a/src/primaite/game/agent/actions/manager.py b/src/primaite/game/agent/actions/manager.py index b89704f4..a6a4f5a6 100644 --- a/src/primaite/game/agent/actions/manager.py +++ b/src/primaite/game/agent/actions/manager.py @@ -28,7 +28,7 @@ class DoNothingAction(AbstractAction, identifier="do_nothing"): """Do Nothing Action.""" class ConfigSchema(AbstractAction.ConfigSchema): - """Configuration Schema for DoNothingAction.""" + """Configuration Schema for do_nothingAction.""" type: str = "do_nothing" @@ -44,6 +44,7 @@ class ActionManager: def __init__( self, actions: List[Dict], # stores list of actions available to agent + nodes: List[Dict], # extra configuration for each node act_map: Optional[ Dict[int, Dict] ] = None, # allows restricting set of possible actions - TODO: Refactor to be a list? @@ -79,6 +80,8 @@ class ActionManager: self.action_map = {i: (a["action"], a["options"]) for i, a in act_map.items()} # make sure all numbers between 0 and N are represented as dict keys in action map assert all([i in self.action_map.keys() for i in range(len(self.action_map))]) + self.node_names: List[str] = [n["node_name"] for n in nodes] + """List of node names in this action space. The list order is the mapping between node index and node name.""" def get_action(self, action: int) -> Tuple[str, Dict]: """Produce action in CAOS format.""" diff --git a/src/primaite/game/agent/actions/node.py b/src/primaite/game/agent/actions/node.py index 4ecc1393..480cb8da 100644 --- a/src/primaite/game/agent/actions/node.py +++ b/src/primaite/game/agent/actions/node.py @@ -34,7 +34,8 @@ class NodeAbstractAction(AbstractAction, identifier="node_abstract"): @classmethod def form_request(cls, config: ConfigSchema) -> RequestFormat: """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - return ["network", "node", config.node_name, cls.config.verb] + print(config) + return ["network", "node", config.node_name, config.verb] class NodeOSScanAction(NodeAbstractAction, identifier="node_os_scan"): diff --git a/src/primaite/game/agent/rewards.py b/src/primaite/game/agent/rewards.py index 1de34b40..f528c851 100644 --- a/src/primaite/game/agent/rewards.py +++ b/src/primaite/game/agent/rewards.py @@ -447,7 +447,7 @@ class SharedReward(AbstractReward): class ActionPenalty(AbstractReward): - """Apply a negative reward when taking any action except DONOTHING.""" + """Apply a negative reward when taking any action except do_nothing.""" def __init__(self, action_penalty: float, do_nothing_penalty: float) -> None: """ @@ -455,9 +455,9 @@ class ActionPenalty(AbstractReward): Reward or penalise agents for doing nothing or taking actions. - :param action_penalty: Reward to give agents for taking any action except DONOTHING + :param action_penalty: Reward to give agents for taking any action except do_nothing :type action_penalty: float - :param do_nothing_penalty: Reward to give agent for taking the DONOTHING action + :param do_nothing_penalty: Reward to give agent for taking the do_nothing action :type do_nothing_penalty: float """ self.action_penalty = action_penalty @@ -473,7 +473,7 @@ class ActionPenalty(AbstractReward): :return: Reward value :rtype: float """ - if last_action_response.action == "DONOTHING": + if last_action_response.action == "do_nothing": return self.do_nothing_penalty else: return self.action_penalty diff --git a/src/primaite/game/agent/scripted_agents/data_manipulation_bot.py b/src/primaite/game/agent/scripted_agents/data_manipulation_bot.py index 129fac1a..c245d687 100644 --- a/src/primaite/game/agent/scripted_agents/data_manipulation_bot.py +++ b/src/primaite/game/agent/scripted_agents/data_manipulation_bot.py @@ -39,7 +39,7 @@ class DataManipulationAgent(AbstractScriptedAgent): """ if timestep < self.next_execution_timestep: self.logger.debug(msg="Performing do NOTHING") - return "DONOTHING", {} + return "do_nothing", {} self._set_next_execution_timestep(timestep + self.agent_settings.start_settings.frequency) self.logger.info(msg="Performing a data manipulation attack!") diff --git a/src/primaite/game/agent/scripted_agents/random_agent.py b/src/primaite/game/agent/scripted_agents/random_agent.py index df9273f7..eade3a0c 100644 --- a/src/primaite/game/agent/scripted_agents/random_agent.py +++ b/src/primaite/game/agent/scripted_agents/random_agent.py @@ -81,4 +81,4 @@ class PeriodicAgent(AbstractScriptedAgent): self._set_next_execution_timestep(timestep + self.settings.frequency, self.settings.variance) return "NODE_APPLICATION_EXECUTE", {"node_id": 0, "application_id": 0} - return "DONOTHING", {} + return "do_nothing", {} diff --git a/src/primaite/game/agent/scripted_agents/tap001.py b/src/primaite/game/agent/scripted_agents/tap001.py index c4f6062a..6d370654 100644 --- a/src/primaite/game/agent/scripted_agents/tap001.py +++ b/src/primaite/game/agent/scripted_agents/tap001.py @@ -46,7 +46,7 @@ class TAP001(AbstractScriptedAgent): :rtype: Tuple[str, Dict] """ if timestep < self.next_execution_timestep: - return "DONOTHING", {} + return "do_nothing", {} self._set_next_execution_timestep(timestep + self.agent_settings.start_settings.frequency) diff --git a/src/primaite/notebooks/Data-Manipulation-E2E-Demonstration.ipynb b/src/primaite/notebooks/Data-Manipulation-E2E-Demonstration.ipynb index 0460f771..89620215 100644 --- a/src/primaite/notebooks/Data-Manipulation-E2E-Demonstration.ipynb +++ b/src/primaite/notebooks/Data-Manipulation-E2E-Demonstration.ipynb @@ -165,13 +165,13 @@ "\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", + "| 0 | domain_controller|\n", + "| 1 | web_server |\n", + "| 2 | database_server |\n", + "| 3 | backup_server |\n", + "| 4 | security_suite |\n", + "| 5 | client_1 |\n", + "| 6 | 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", diff --git a/tests/integration_tests/game_layer/actions/test_c2_suite_actions.py b/tests/integration_tests/game_layer/actions/test_c2_suite_actions.py index 187fb1fe..d73c9834 100644 --- a/tests/integration_tests/game_layer/actions/test_c2_suite_actions.py +++ b/tests/integration_tests/game_layer/actions/test_c2_suite_actions.py @@ -134,7 +134,7 @@ def test_c2_server_ransomware(game_and_agent_fixture: Tuple[PrimaiteGame, ProxyA # Stepping a few timesteps to allow for the RansowmareScript to finish installing. - action = ("DONOTHING", {}) + action = ("do_nothing", {}) agent.store_action(action) game.step() game.step() diff --git a/tests/integration_tests/game_layer/actions/test_node_request_permission.py b/tests/integration_tests/game_layer/actions/test_node_request_permission.py index fdf04ad5..c34103bc 100644 --- a/tests/integration_tests/game_layer/actions/test_node_request_permission.py +++ b/tests/integration_tests/game_layer/actions/test_node_request_permission.py @@ -36,7 +36,7 @@ def test_node_startup_shutdown(game_and_agent_fixture: Tuple[PrimaiteGame, Proxy assert client_1.operating_state == NodeOperatingState.SHUTTING_DOWN for i in range(client_1.shut_down_duration + 1): - action = ("DONOTHING", {"node_id": 0}) + action = ("do_nothing", {"node_id": 0}) agent.store_action(action) game.step() @@ -50,7 +50,7 @@ def test_node_startup_shutdown(game_and_agent_fixture: Tuple[PrimaiteGame, Proxy assert client_1.operating_state == NodeOperatingState.BOOTING for i in range(client_1.start_up_duration + 1): - action = ("DONOTHING", {"node_id": 0}) + action = ("do_nothing", {"node_id": 0}) agent.store_action(action) game.step() @@ -80,7 +80,7 @@ def test_node_cannot_be_shut_down_if_node_is_already_off(game_and_agent_fixture: client_1.power_off() for i in range(client_1.shut_down_duration + 1): - action = ("DONOTHING", {"node_id": 0}) + action = ("do_nothing", {"node_id": 0}) agent.store_action(action) game.step() diff --git a/tests/integration_tests/game_layer/test_RNG_seed.py b/tests/integration_tests/game_layer/test_RNG_seed.py index 0c6d567d..e772af32 100644 --- a/tests/integration_tests/game_layer/test_RNG_seed.py +++ b/tests/integration_tests/game_layer/test_RNG_seed.py @@ -24,12 +24,12 @@ def test_rng_seed_set(create_env): env.reset(seed=3) for i in range(100): env.step(0) - a = [item.timestep for item in env.game.agents["client_2_green_user"].history if item.action != "DONOTHING"] + a = [item.timestep for item in env.game.agents["client_2_green_user"].history if item.action != "do_nothing"] env.reset(seed=3) for i in range(100): env.step(0) - b = [item.timestep for item in env.game.agents["client_2_green_user"].history if item.action != "DONOTHING"] + b = [item.timestep for item in env.game.agents["client_2_green_user"].history if item.action != "do_nothing"] assert a == b @@ -40,11 +40,11 @@ def test_rng_seed_unset(create_env): env.reset() for i in range(100): env.step(0) - a = [item.timestep for item in env.game.agents["client_2_green_user"].history if item.action != "DONOTHING"] + a = [item.timestep for item in env.game.agents["client_2_green_user"].history if item.action != "do_nothing"] env.reset() for i in range(100): env.step(0) - b = [item.timestep for item in env.game.agents["client_2_green_user"].history if item.action != "DONOTHING"] + b = [item.timestep for item in env.game.agents["client_2_green_user"].history if item.action != "do_nothing"] assert a != b diff --git a/tests/integration_tests/game_layer/test_action_mask.py b/tests/integration_tests/game_layer/test_action_mask.py index 64464724..7a1475c2 100644 --- a/tests/integration_tests/game_layer/test_action_mask.py +++ b/tests/integration_tests/game_layer/test_action_mask.py @@ -91,7 +91,7 @@ def test_mask_contents_correct(): assert mask[action_num] node_obj.operating_state = NodeOperatingState.ON - if act_type == "DONOTHING": + if act_type == "do_nothing": assert mask[action_num] if act_type == "NODE_SERVICE_DISABLE": diff --git a/tests/integration_tests/game_layer/test_actions.py b/tests/integration_tests/game_layer/test_actions.py index 9fdf029b..859c056c 100644 --- a/tests/integration_tests/game_layer/test_actions.py +++ b/tests/integration_tests/game_layer/test_actions.py @@ -32,7 +32,7 @@ FIREWALL_ACTIONS_NETWORK = TEST_ASSETS_ROOT / "configs/firewall_actions_network. def test_do_nothing_integration(game_and_agent: Tuple[PrimaiteGame, ProxyAgent]): - """Test that the DoNothingAction can form a request and that it is accepted by the simulation.""" + """Test that the do_nothingAction can form a request and that it is accepted by the simulation.""" game, agent = game_and_agent action = ("do_nothing", {}) diff --git a/tests/integration_tests/game_layer/test_rewards.py b/tests/integration_tests/game_layer/test_rewards.py index 0005b508..882c0923 100644 --- a/tests/integration_tests/game_layer/test_rewards.py +++ b/tests/integration_tests/game_layer/test_rewards.py @@ -31,7 +31,7 @@ def test_WebpageUnavailablePenalty(game_and_agent): agent.reward_function.register_component(comp, 0.7) # Check that before trying to fetch the webpage, the reward is 0.0 - agent.store_action(("DONOTHING", {})) + agent.store_action(("do_nothing", {})) game.step() assert agent.reward_function.current_reward == 0.0 @@ -149,7 +149,7 @@ def test_action_penalty(): # Create an ActionPenalty Reward Penalty = ActionPenalty(action_penalty=-0.75, do_nothing_penalty=0.125) - # Assert that penalty is applied if action isn't DONOTHING + # Assert that penalty is applied if action isn't do_nothing reward_value = Penalty.calculate( state={}, last_action_response=AgentHistoryItem( @@ -163,12 +163,12 @@ def test_action_penalty(): assert reward_value == -0.75 - # Assert that no penalty applied for a DONOTHING action + # Assert that no penalty applied for a do_nothing action reward_value = Penalty.calculate( state={}, last_action_response=AgentHistoryItem( timestep=0, - action="DONOTHING", + action="do_nothing", parameters={}, request=["do_nothing"], response=RequestResponse.from_bool(True), @@ -186,7 +186,7 @@ def test_action_penalty_e2e(game_and_agent): agent.reward_function.register_component(comp, 1.0) - action = ("DONOTHING", {}) + action = ("do_nothing", {}) agent.store_action(action) game.step() assert agent.reward_function.current_reward == 0.125 diff --git a/tests/unit_tests/_primaite/_game/_agent/test_actions.py b/tests/unit_tests/_primaite/_game/_agent/test_actions.py index c2d31ee1..46963015 100644 --- a/tests/unit_tests/_primaite/_game/_agent/test_actions.py +++ b/tests/unit_tests/_primaite/_game/_agent/test_actions.py @@ -5,7 +5,7 @@ import pytest from primaite.game.agent.actions import ( ActionManager, - DoNothingAction, + do_nothingAction, NodeServiceDisableAction, NodeServiceEnableAction, NodeServicePauseAction, @@ -18,10 +18,10 @@ from primaite.game.agent.actions import ( def test_do_nothing_action_form_request(): - """Test that the DoNothingAction can form a request and that it is correct.""" + """Test that the do_nothingAction can form a request and that it is correct.""" manager = Mock() - action = DoNothingAction(manager=manager) + action = do_nothingAction(manager=manager) request = action.form_request() diff --git a/tests/unit_tests/_primaite/_game/_agent/test_sticky_rewards.py b/tests/unit_tests/_primaite/_game/_agent/test_sticky_rewards.py index 58f0fcc1..78113f5f 100644 --- a/tests/unit_tests/_primaite/_game/_agent/test_sticky_rewards.py +++ b/tests/unit_tests/_primaite/_game/_agent/test_sticky_rewards.py @@ -70,7 +70,7 @@ class TestWebpageUnavailabilitySticky: reward = WebpageUnavailablePenalty("computer", sticky=False) # no response codes yet, reward is 0 - action, params, request = "DO_NOTHING", {}, ["DONOTHING"] + action, params, request = "DO_NOTHING", {}, ["do_nothing"] response = RequestResponse(status="success", data={}) browser_history = [] state = {"network": {"nodes": {"computer": {"applications": {"WebBrowser": {"history": browser_history}}}}}} @@ -93,7 +93,7 @@ class TestWebpageUnavailabilitySticky: # THE IMPORTANT BIT # agent did nothing, because reward is not sticky, it goes back to 0 - action, params, request = "DO_NOTHING", {}, ["DONOTHING"] + action, params, request = "DO_NOTHING", {}, ["do_nothing"] response = RequestResponse(status="success", data={}) browser_history = [] state = {"network": {"nodes": {"computer": {"applications": {"WebBrowser": {"history": browser_history}}}}}} @@ -130,7 +130,7 @@ class TestWebpageUnavailabilitySticky: reward = WebpageUnavailablePenalty("computer", sticky=True) # no response codes yet, reward is 0 - action, params, request = "DO_NOTHING", {}, ["DONOTHING"] + action, params, request = "DO_NOTHING", {}, ["do_nothing"] response = RequestResponse(status="success", data={}) browser_history = [] state = {"network": {"nodes": {"computer": {"applications": {"WebBrowser": {"history": browser_history}}}}}} @@ -153,7 +153,7 @@ class TestWebpageUnavailabilitySticky: # THE IMPORTANT BIT # agent did nothing, because reward is sticky, it stays at 1.0 - action, params, request = "DO_NOTHING", {}, ["DONOTHING"] + action, params, request = "DO_NOTHING", {}, ["do_nothing"] response = RequestResponse(status="success", data={}) state = {"network": {"nodes": {"computer": {"applications": {"WebBrowser": {"history": browser_history}}}}}} last_action_response = AgentHistoryItem( @@ -191,7 +191,7 @@ class TestGreenAdminDatabaseUnreachableSticky: reward = GreenAdminDatabaseUnreachablePenalty("computer", sticky=False) # no response codes yet, reward is 0 - action, params, request = "DO_NOTHING", {}, ["DONOTHING"] + action, params, request = "DO_NOTHING", {}, ["do_nothing"] response = RequestResponse(status="success", data={}) state = {"network": {"nodes": {"computer": {"applications": {"DatabaseClient": {}}}}}} last_action_response = AgentHistoryItem( @@ -212,7 +212,7 @@ class TestGreenAdminDatabaseUnreachableSticky: # THE IMPORTANT BIT # agent did nothing, because reward is not sticky, it goes back to 0 - action, params, request = "DO_NOTHING", {}, ["DONOTHING"] + action, params, request = "DO_NOTHING", {}, ["do_nothing"] response = RequestResponse(status="success", data={}) browser_history = [] state = {"network": {"nodes": {"computer": {"applications": {"DatabaseClient": {}}}}}} @@ -247,7 +247,7 @@ class TestGreenAdminDatabaseUnreachableSticky: reward = GreenAdminDatabaseUnreachablePenalty("computer", sticky=True) # no response codes yet, reward is 0 - action, params, request = "DO_NOTHING", {}, ["DONOTHING"] + action, params, request = "DO_NOTHING", {}, ["do_nothing"] response = RequestResponse(status="success", data={}) state = {"network": {"nodes": {"computer": {"applications": {"DatabaseClient": {}}}}}} last_action_response = AgentHistoryItem( @@ -268,7 +268,7 @@ class TestGreenAdminDatabaseUnreachableSticky: # THE IMPORTANT BIT # agent did nothing, because reward is not sticky, it goes back to 0 - action, params, request = "DO_NOTHING", {}, ["DONOTHING"] + action, params, request = "DO_NOTHING", {}, ["do_nothing"] response = RequestResponse(status="success", data={}) state = {"network": {"nodes": {"computer": {"applications": {"DatabaseClient": {}}}}}} last_action_response = AgentHistoryItem( From 3c0a70be717e7f20b08fbd65e25d98820b89d81c Mon Sep 17 00:00:00 2001 From: Charlie Crane Date: Fri, 13 Dec 2024 09:49:21 +0000 Subject: [PATCH 50/58] #2912 - Changes for extensible actions --- src/primaite/game/agent/actions/acl.py | 20 ------------------- .../scripted_agents/data_manipulation_bot.py | 11 ++++++++-- 2 files changed, 9 insertions(+), 22 deletions(-) diff --git a/src/primaite/game/agent/actions/acl.py b/src/primaite/game/agent/actions/acl.py index 37dde757..d2846ddb 100644 --- a/src/primaite/game/agent/actions/acl.py +++ b/src/primaite/game/agent/actions/acl.py @@ -76,28 +76,8 @@ class ACLRemoveRuleAbstractAction(AbstractAction, identifier="acl_remove_rule_ab class ConfigSchema(AbstractAction.ConfigSchema): """Configuration Schema base for ACL remove rule abstract actions.""" - src_ip: str - protocol_name: str position: int - @field_validator( - "src_ip", - mode="before", - ) - @classmethod - def valid_ip(cls, v: str) -> str: - """Check that a valid IP has been provided for src and dst.""" - return ipv4_validator(v) - - @field_validator( - "protocol_name", - mode="before", - ) - @classmethod - def is_valid_protocol(cls, v: str) -> bool: - """Check that we are using a valid protocol.""" - return protocol_validator(v) - class RouterACLAddRuleAction(ACLAddRuleAbstractAction, identifier="router_acl_add_rule"): """Action which adds a rule to a router's ACL.""" diff --git a/src/primaite/game/agent/scripted_agents/data_manipulation_bot.py b/src/primaite/game/agent/scripted_agents/data_manipulation_bot.py index c245d687..eb0ce957 100644 --- a/src/primaite/game/agent/scripted_agents/data_manipulation_bot.py +++ b/src/primaite/game/agent/scripted_agents/data_manipulation_bot.py @@ -38,12 +38,18 @@ class DataManipulationAgent(AbstractScriptedAgent): :rtype: Tuple[str, Dict] """ if timestep < self.next_execution_timestep: - self.logger.debug(msg="Performing do NOTHING") + self.logger.debug(msg="Performing do nothing") return "do_nothing", {} self._set_next_execution_timestep(timestep + self.agent_settings.start_settings.frequency) self.logger.info(msg="Performing a data manipulation attack!") - return "NODE_APPLICATION_EXECUTE", {"node_id": self.starting_node_idx, "application_id": 0} + self.logger.info(msg=f"Chosen to attack {self.starting_node}") + # TODO: Why is the application_id hardcoded to target application 0? + return "node_application_execute", { + "type": "node_application_execute", + "node_name": self.starting_node, + "application_name": "test", + } def setup_agent(self) -> None: """Set the next execution timestep when the episode resets.""" @@ -55,4 +61,5 @@ class DataManipulationAgent(AbstractScriptedAgent): # we are assuming that every node in the node manager has a data manipulation application at idx 0 num_nodes = len(self.action_manager.node_names) self.starting_node_idx = random.randint(0, num_nodes - 1) + self.starting_node = self.action_manager.node_names[self.starting_node_idx] self.logger.debug(msg=f"Select Start Node ID: {self.starting_node_idx}") From c9752f0dc5e94a7dd4f0b58004e03096648888d1 Mon Sep 17 00:00:00 2001 From: Marek Wolan Date: Fri, 3 Jan 2025 11:22:17 +0000 Subject: [PATCH 51/58] #2913 - minor comment cleanup --- docs/source/how_to_guides/extensible_rewards.rst | 2 +- src/primaite/game/agent/rewards.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/how_to_guides/extensible_rewards.rst b/docs/source/how_to_guides/extensible_rewards.rst index 4dd24110..a01b9d8f 100644 --- a/docs/source/how_to_guides/extensible_rewards.rst +++ b/docs/source/how_to_guides/extensible_rewards.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK .. _about: diff --git a/src/primaite/game/agent/rewards.py b/src/primaite/game/agent/rewards.py index c5850d6e..a4c7c546 100644 --- a/src/primaite/game/agent/rewards.py +++ b/src/primaite/game/agent/rewards.py @@ -223,7 +223,7 @@ class WebpageUnavailablePenalty(AbstractReward, identifier="WEBPAGE_UNAVAILABLE_ """Penalises the agent when the web browser fails to fetch a webpage.""" config: "WebpageUnavailablePenalty.ConfigSchema" - reward: float = 0.0 # XXX: Private attribute? + reward: float = 0.0 location_in_state: List[str] = [""] # Calculate in __init__()? class ConfigSchema(AbstractReward.ConfigSchema): From b11678a128183dbd1badaf663c2f57446f6258f3 Mon Sep 17 00:00:00 2001 From: Charlie Crane Date: Fri, 3 Jan 2025 14:40:00 +0000 Subject: [PATCH 52/58] #2912 - Actioning Review Comments --- benchmark/benchmark.py | 2 +- benchmark/primaite_benchmark.py | 2 +- benchmark/report.py | 2 +- benchmark/utils.py | 2 +- docs/_templates/custom-class-template.rst | 2 +- docs/_templates/custom-module-template.rst | 2 +- docs/api.rst | 2 +- docs/conf.py | 2 +- docs/index.rst | 2 +- docs/source/action_masking.rst | 2 +- docs/source/config.rst | 2 +- docs/source/configuration/agents.rst | 2 +- docs/source/configuration/game.rst | 2 +- docs/source/configuration/io_settings.rst | 2 +- docs/source/configuration/simulation.rst | 2 +- .../simulation/nodes/common/common.rst | 2 +- .../common/common_host_node_attributes.rst | 2 +- .../common/common_network_node_attributes.rst | 2 +- .../nodes/common/common_node_attributes.rst | 2 +- .../nodes/common/node_type_list.rst | 2 +- .../simulation/nodes/computer.rst | 2 +- .../simulation/nodes/firewall.rst | 2 +- .../simulation/nodes/network_examples.rst | 2 +- .../configuration/simulation/nodes/router.rst | 2 +- .../configuration/simulation/nodes/server.rst | 2 +- .../configuration/simulation/nodes/switch.rst | 2 +- .../simulation/software/applications.rst | 2 +- .../simulation/software/services.rst | 2 +- docs/source/customising_scenarios.rst | 2 +- docs/source/dependencies.rst | 2 +- docs/source/developer_tools.rst | 2 +- docs/source/environment.rst | 2 +- docs/source/example_notebooks.rst | 2 +- docs/source/game_layer.rst | 2 +- docs/source/getting_started.rst | 2 +- docs/source/glossary.rst | 2 +- .../how_to_guides/extensible_actions.rst | 2 +- docs/source/node_sets.rst | 2 +- docs/source/notebooks/executed_notebooks.rst | 2 +- docs/source/primaite-dependencies.rst | 2 +- docs/source/request_system.rst | 2 +- docs/source/rewards.rst | 2 +- docs/source/simulation.rst | 2 +- .../network/airspace.rst | 2 +- .../network/base_hardware.rst | 2 +- .../simulation_components/network/network.rst | 2 +- .../network/network_interfaces.rst | 2 +- .../network/nodes/firewall.rst | 2 +- .../network/nodes/host_node.rst | 2 +- .../network/nodes/network_node.rst | 2 +- .../network/nodes/router.rst | 2 +- .../network/nodes/switch.rst | 2 +- .../network/nodes/wireless_router.rst | 2 +- .../network/transport_to_data_link_layer.rst | 2 +- .../system/applications/c2_suite.rst | 2 +- .../applications/data_manipulation_bot.rst | 2 +- .../system/applications/database_client.rst | 2 +- .../system/applications/dos_bot.rst | 2 +- .../system/applications/nmap.rst | 2 +- .../system/applications/ransomware_script.rst | 2 +- .../system/applications/web_browser.rst | 2 +- .../system/common/common_configuration.rst | 2 +- .../system/common/db_payload_list.rst | 2 +- .../system/internal_frame_processing.rst | 2 +- .../system/list_of_applications.rst | 2 +- .../system/list_of_services.rst | 2 +- .../system/list_of_system_applications.rst | 2 +- .../system/list_of_system_services.rst | 2 +- .../simulation_components/system/pcap.rst | 2 +- .../system/services/database_service.rst | 2 +- .../system/services/dns_client.rst | 2 +- .../system/services/dns_server.rst | 2 +- .../system/services/ftp_client.rst | 2 +- .../system/services/ftp_server.rst | 2 +- .../system/services/ntp_client.rst | 2 +- .../system/services/ntp_server.rst | 2 +- .../system/services/terminal.rst | 2 +- .../system/services/web_server.rst | 2 +- .../system/session_and_software_manager.rst | 2 +- .../simulation_components/system/software.rst | 2 +- .../simulation_components/system/sys_log.rst | 2 +- docs/source/simulation_structure.rst | 2 +- docs/source/state_system.rst | 2 +- docs/source/varying_config_files.rst | 2 +- src/primaite/__init__.py | 2 +- src/primaite/_legacy/actions.py | 2 +- src/primaite/cli.py | 2 +- src/primaite/config/__init__.py | 2 +- src/primaite/config/load.py | 2 +- src/primaite/exceptions.py | 2 +- src/primaite/game/__init__.py | 2 +- src/primaite/game/agent/__init__.py | 2 +- src/primaite/game/agent/actions/__init__.py | 2 +- src/primaite/game/agent/actions/abstract.py | 2 +- src/primaite/game/agent/actions/acl.py | 2 +- .../game/agent/actions/application.py | 2 +- src/primaite/game/agent/actions/config.py | 2 +- src/primaite/game/agent/actions/file.py | 2 +- src/primaite/game/agent/actions/folder.py | 2 +- src/primaite/game/agent/actions/host_nic.py | 2 +- src/primaite/game/agent/actions/manager.py | 4 +-- src/primaite/game/agent/actions/network.py | 2 +- src/primaite/game/agent/actions/node.py | 2 +- src/primaite/game/agent/actions/service.py | 2 +- src/primaite/game/agent/actions/session.py | 2 +- src/primaite/game/agent/agent_log.py | 2 +- src/primaite/game/agent/interface.py | 2 +- .../game/agent/observations/__init__.py | 2 +- .../agent/observations/acl_observation.py | 2 +- .../observations/file_system_observations.py | 2 +- .../observations/firewall_observation.py | 2 +- .../agent/observations/host_observations.py | 2 +- .../agent/observations/link_observation.py | 2 +- .../agent/observations/nic_observations.py | 2 +- .../agent/observations/node_observations.py | 2 +- .../agent/observations/observation_manager.py | 2 +- .../game/agent/observations/observations.py | 2 +- .../agent/observations/router_observation.py | 2 +- .../observations/software_observation.py | 2 +- src/primaite/game/agent/rewards.py | 2 +- .../game/agent/scripted_agents/__init__.py | 2 +- .../scripted_agents/data_manipulation_bot.py | 2 +- .../scripted_agents/probabilistic_agent.py | 2 +- .../agent/scripted_agents/random_agent.py | 2 +- .../game/agent/scripted_agents/tap001.py | 2 +- src/primaite/game/agent/utils.py | 2 +- src/primaite/game/game.py | 2 +- src/primaite/game/science.py | 2 +- src/primaite/interface/__init__.py | 2 +- src/primaite/interface/request.py | 2 +- src/primaite/session/__init__.py | 2 +- src/primaite/session/environment.py | 2 +- src/primaite/session/episode_schedule.py | 2 +- src/primaite/session/io.py | 2 +- src/primaite/session/ray_envs.py | 2 +- src/primaite/setup/__init__.py | 2 +- src/primaite/setup/reset_demo_notebooks.py | 2 +- src/primaite/setup/reset_example_configs.py | 2 +- src/primaite/simulator/__init__.py | 2 +- src/primaite/simulator/core.py | 2 +- src/primaite/simulator/domain/__init__.py | 2 +- src/primaite/simulator/domain/account.py | 2 +- src/primaite/simulator/domain/controller.py | 2 +- .../simulator/file_system/__init__.py | 2 +- src/primaite/simulator/file_system/file.py | 2 +- .../simulator/file_system/file_system.py | 2 +- .../file_system/file_system_item_abc.py | 2 +- .../simulator/file_system/file_type.py | 2 +- src/primaite/simulator/file_system/folder.py | 2 +- src/primaite/simulator/network/__init__.py | 2 +- src/primaite/simulator/network/airspace.py | 2 +- src/primaite/simulator/network/container.py | 2 +- src/primaite/simulator/network/creation.py | 2 +- .../simulator/network/hardware/__init__.py | 2 +- .../simulator/network/hardware/base.py | 2 +- .../hardware/network_interface/__init__.py | 2 +- .../network_interface/wireless/__init__.py | 2 +- .../wireless/wireless_access_point.py | 2 +- .../wireless/wireless_nic.py | 2 +- .../network/hardware/node_operating_state.py | 2 +- .../network/hardware/nodes/__init__.py | 2 +- .../network/hardware/nodes/host/__init__.py | 2 +- .../network/hardware/nodes/host/computer.py | 2 +- .../network/hardware/nodes/host/host_node.py | 2 +- .../network/hardware/nodes/host/server.py | 2 +- .../hardware/nodes/network/__init__.py | 2 +- .../hardware/nodes/network/firewall.py | 2 +- .../hardware/nodes/network/network_node.py | 2 +- .../network/hardware/nodes/network/router.py | 2 +- .../network/hardware/nodes/network/switch.py | 2 +- .../hardware/nodes/network/wireless_router.py | 2 +- src/primaite/simulator/network/networks.py | 2 +- src/primaite/simulator/network/nmne.py | 2 +- .../simulator/network/protocols/__init__.py | 2 +- .../simulator/network/protocols/arp.py | 2 +- .../simulator/network/protocols/dns.py | 2 +- .../simulator/network/protocols/ftp.py | 2 +- .../simulator/network/protocols/http.py | 2 +- .../simulator/network/protocols/icmp.py | 2 +- .../simulator/network/protocols/masquerade.py | 2 +- .../simulator/network/protocols/ntp.py | 2 +- .../simulator/network/protocols/packet.py | 2 +- .../simulator/network/protocols/ssh.py | 2 +- .../network/transmission/__init__.py | 2 +- .../network/transmission/data_link_layer.py | 2 +- .../network/transmission/network_layer.py | 2 +- .../network/transmission/primaite_layer.py | 2 +- .../network/transmission/transport_layer.py | 2 +- src/primaite/simulator/network/utils.py | 2 +- src/primaite/simulator/sim_container.py | 2 +- src/primaite/simulator/system/__init__.py | 2 +- .../simulator/system/applications/__init__.py | 2 +- .../system/applications/application.py | 2 +- .../system/applications/database_client.py | 2 +- .../simulator/system/applications/nmap.py | 2 +- .../applications/red_applications/__init__.py | 2 +- .../red_applications/c2/__init__.py | 2 +- .../red_applications/c2/abstract_c2.py | 2 +- .../red_applications/c2/c2_beacon.py | 2 +- .../red_applications/c2/c2_server.py | 2 +- .../red_applications/data_manipulation_bot.py | 2 +- .../applications/red_applications/dos_bot.py | 2 +- .../red_applications/ransomware_script.py | 2 +- .../system/applications/web_browser.py | 2 +- .../simulator/system/core/__init__.py | 2 +- .../simulator/system/core/packet_capture.py | 2 +- .../simulator/system/core/session_manager.py | 2 +- .../simulator/system/core/software_manager.py | 2 +- src/primaite/simulator/system/core/sys_log.py | 2 +- .../simulator/system/processes/__init__.py | 2 +- .../simulator/system/processes/process.py | 2 +- .../simulator/system/services/__init__.py | 2 +- .../system/services/access/__init__.py | 2 +- .../system/services/access/user_manager.py | 2 +- .../services/access/user_session_manager.py | 2 +- .../simulator/system/services/arp/__init__.py | 2 +- .../simulator/system/services/arp/arp.py | 2 +- .../system/services/database/__init__.py | 2 +- .../services/database/database_service.py | 2 +- .../simulator/system/services/dns/__init__.py | 2 +- .../system/services/dns/dns_client.py | 2 +- .../system/services/dns/dns_server.py | 2 +- .../simulator/system/services/ftp/__init__.py | 2 +- .../system/services/ftp/ftp_client.py | 2 +- .../system/services/ftp/ftp_server.py | 2 +- .../system/services/ftp/ftp_service.py | 2 +- .../system/services/icmp/__init__.py | 2 +- .../simulator/system/services/icmp/icmp.py | 2 +- .../system/services/icmp/router_icmp.py | 2 +- .../simulator/system/services/ntp/__init__.py | 2 +- .../system/services/ntp/ntp_client.py | 2 +- .../system/services/ntp/ntp_server.py | 2 +- .../simulator/system/services/service.py | 2 +- .../system/services/terminal/__init__.py | 2 +- .../system/services/terminal/terminal.py | 2 +- .../system/services/web_server/__init__.py | 2 +- .../system/services/web_server/web_server.py | 2 +- src/primaite/simulator/system/software.py | 2 +- src/primaite/utils/__init__.py | 2 +- src/primaite/utils/cli/__init__.py | 2 +- src/primaite/utils/cli/dev_cli.py | 2 +- .../utils/cli/primaite_config_utils.py | 2 +- src/primaite/utils/converters.py | 2 +- src/primaite/utils/package_data.py | 2 +- src/primaite/utils/session_metadata_parser.py | 2 +- src/primaite/utils/session_output_reader.py | 2 +- src/primaite/utils/session_output_writer.py | 2 +- src/primaite/utils/validation/__init__.py | 2 +- src/primaite/utils/validation/ip_protocol.py | 2 +- src/primaite/utils/validation/ipv4_address.py | 2 +- src/primaite/utils/validation/port.py | 2 +- tests/__init__.py | 2 +- tests/conftest.py | 29 +------------------ tests/e2e_integration_tests/__init__.py | 2 +- .../action_masking/__init__.py | 2 +- .../test_agents_use_action_masks.py | 2 +- .../environments/__init__.py | 2 +- .../test_rllib_multi_agent_environment.py | 2 +- .../test_rllib_single_agent_environment.py | 2 +- .../environments/test_sb3_environment.py | 2 +- .../e2e_integration_tests/test_environment.py | 2 +- .../test_uc2_data_manipulation_scenario.py | 2 +- tests/integration_tests/__init__.py | 2 +- tests/integration_tests/cli/__init__.py | 2 +- tests/integration_tests/cli/test_dev_cli.py | 2 +- .../component_creation/__init__.py | 2 +- .../test_action_integration.py | 2 +- .../test_permission_system.py | 2 +- .../configuration_file_parsing/__init__.py | 2 +- .../nodes/__init__.py | 2 +- .../nodes/network/__init__.py | 2 +- .../nodes/network/test_firewall_config.py | 2 +- .../nodes/network/test_router_config.py | 2 +- .../nodes/test_node_config.py | 2 +- ...software_installation_and_configuration.py | 2 +- .../test_episode_scheduler.py | 2 +- .../test_game_options_config.py | 2 +- .../test_io_settings.py | 2 +- .../test_no_nodes_links_agents_config.py | 2 +- .../test_software_fix_duration.py | 2 +- .../applications/extended_application.py | 2 +- .../extensions/nodes/giga_switch.py | 2 +- .../extensions/nodes/super_computer.py | 2 +- .../extensions/services/extended_service.py | 2 +- .../extensions/test_extendable_config.py | 2 +- .../game_layer/actions/__init__.py | 2 +- .../test_application_request_permission.py | 2 +- .../actions/test_c2_suite_actions.py | 2 +- .../actions/test_configure_actions.py | 2 +- .../actions/test_file_request_permission.py | 2 +- .../actions/test_folder_request_permission.py | 2 +- .../actions/test_nic_request_permission.py | 2 +- .../actions/test_node_request_permission.py | 2 +- .../test_service_request_permission.py | 2 +- .../actions/test_terminal_actions.py | 2 +- .../game_layer/observations/__init__.py | 2 +- .../observations/test_acl_observations.py | 2 +- .../test_file_system_observations.py | 2 +- .../observations/test_firewall_observation.py | 2 +- .../observations/test_link_observations.py | 2 +- .../observations/test_nic_observations.py | 2 +- .../observations/test_node_observations.py | 2 +- .../observations/test_router_observation.py | 2 +- .../test_software_observations.py | 2 +- .../observations/test_user_observations.py | 2 +- .../game_layer/test_RNG_seed.py | 2 +- .../game_layer/test_action_mask.py | 2 +- .../game_layer/test_actions.py | 2 +- .../game_layer/test_observations.py | 2 +- .../game_layer/test_rewards.py | 2 +- tests/integration_tests/network/__init__.py | 2 +- .../network/test_airspace_config.py | 2 +- ...ndwidth_load_checks_before_transmission.py | 2 +- .../network/test_broadcast.py | 2 +- .../network/test_capture_nmne.py | 2 +- .../network/test_firewall.py | 2 +- .../network/test_frame_transmission.py | 2 +- ...test_multi_lan_internet_example_network.py | 2 +- .../network/test_network_creation.py | 2 +- .../network/test_nic_link_connection.py | 2 +- .../integration_tests/network/test_routing.py | 2 +- .../network/test_switched_network.py | 2 +- .../test_users_creation_from_config.py | 2 +- .../network/test_wireless_router.py | 2 +- tests/integration_tests/system/__init__.py | 2 +- .../test_c2_suite_integration.py | 2 +- .../test_data_manipulation_bot_and_server.py | 2 +- .../test_dos_bot_and_server.py | 2 +- .../test_ransomware_script.py | 2 +- .../system/test_application_on_node.py | 2 +- tests/integration_tests/system/test_arp.py | 2 +- .../system/test_database_on_node.py | 2 +- .../system/test_dns_client_server.py | 2 +- .../system/test_ftp_client_server.py | 2 +- tests/integration_tests/system/test_nmap.py | 2 +- .../system/test_ntp_client_server.py | 2 +- .../system/test_service_listening_on_ports.py | 2 +- .../system/test_service_on_node.py | 2 +- .../test_user_session_manager_logins.py | 2 +- .../system/test_web_client_server.py | 2 +- .../test_web_client_server_and_database.py | 2 +- .../test_simulation/__init__.py | 2 +- .../test_simulation/test_request_response.py | 2 +- tests/mock_and_patch/__init__.py | 2 +- tests/mock_and_patch/get_session_path_mock.py | 2 +- tests/unit_tests/__init__.py | 2 +- tests/unit_tests/_primaite/__init__.py | 2 +- tests/unit_tests/_primaite/_game/__init__.py | 2 +- .../_primaite/_game/_agent/__init__.py | 2 +- .../_primaite/_game/_agent/test_actions.py | 2 +- .../_primaite/_game/_agent/test_agent_log.py | 2 +- .../_game/_agent/test_observations.py | 2 +- .../_game/_agent/test_probabilistic_agent.py | 2 +- .../_game/_agent/test_sticky_rewards.py | 2 +- .../_primaite/_interface/__init__.py | 2 +- .../_primaite/_interface/test_request.py | 2 +- .../unit_tests/_primaite/_session/__init__.py | 2 +- .../_session/test_episode_schedule.py | 2 +- .../_primaite/_simulator/__init__.py | 2 +- .../_primaite/_simulator/_domain/__init__.py | 2 +- .../_simulator/_domain/test_account.py | 2 +- .../_simulator/_domain/test_controller.py | 2 +- .../_simulator/_file_system/__init__.py | 2 +- .../_simulator/_file_system/test_file.py | 2 +- .../_file_system/test_file_actions.py | 2 +- .../_file_system/test_file_system.py | 2 +- .../_file_system/test_file_system_actions.py | 2 +- .../_simulator/_file_system/test_folder.py | 2 +- .../_file_system/test_folder_actions.py | 2 +- .../_primaite/_simulator/_network/__init__.py | 2 +- .../_simulator/_network/_hardware/__init__.py | 2 +- .../_network/_hardware/nodes/__init__.py | 2 +- .../_network/_hardware/nodes/test_acl.py | 2 +- .../_network/_hardware/nodes/test_router.py | 2 +- .../_network/_hardware/nodes/test_switch.py | 2 +- .../test_network_interface_actions.py | 2 +- .../_simulator/_network/_hardware/test_nic.py | 2 +- .../_network/_hardware/test_node_actions.py | 2 +- .../_network/_transmission/__init__.py | 2 +- .../_transmission/test_data_link_layer.py | 2 +- .../_transmission/test_network_layer.py | 2 +- .../_simulator/_network/test_container.py | 2 +- .../_simulator/_network/test_creation.py | 2 +- .../_simulator/_network/test_utils.py | 2 +- .../_primaite/_simulator/_system/__init__.py | 2 +- .../_system/_applications/__init__.py | 2 +- .../_red_applications/__init__.py | 2 +- .../_red_applications/test_c2_suite.py | 2 +- .../test_data_manipulation_bot.py | 2 +- .../_red_applications/test_dos_bot.py | 2 +- .../_applications/test_application_actions.py | 2 +- .../test_application_registry.py | 2 +- .../_applications/test_applications.py | 2 +- .../_applications/test_database_client.py | 2 +- .../_system/_applications/test_web_browser.py | 2 +- .../_simulator/_system/_services/__init__.py | 2 +- .../_system/_services/test_database.py | 2 +- .../_system/_services/test_dns_client.py | 2 +- .../_system/_services/test_dns_server.py | 2 +- .../_system/_services/test_ftp_client.py | 2 +- .../_system/_services/test_ftp_server.py | 2 +- .../_system/_services/test_service_actions.py | 2 +- .../_system/_services/test_services.py | 2 +- .../_system/_services/test_terminal.py | 2 +- .../_system/_services/test_web_server.py | 2 +- .../_simulator/_system/core/test_sys_log.py | 2 +- .../_simulator/_system/test_software.py | 2 +- .../_primaite/_simulator/test_core.py | 2 +- .../_simulator/test_sim_container.py | 2 +- tests/unit_tests/_primaite/_utils/__init__.py | 2 +- .../_primaite/_utils/_validation/__init__.py | 2 +- .../_utils/_validation/test_ip_protocol.py | 2 +- .../_primaite/_utils/_validation/test_port.py | 2 +- .../_utils/test_dict_enum_keys_conversion.py | 2 +- 414 files changed, 414 insertions(+), 443 deletions(-) diff --git a/benchmark/benchmark.py b/benchmark/benchmark.py index 4ad398b9..ddedebb7 100644 --- a/benchmark/benchmark.py +++ b/benchmark/benchmark.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import Any, Dict, Optional, Tuple from gymnasium.core import ObsType diff --git a/benchmark/primaite_benchmark.py b/benchmark/primaite_benchmark.py index 86ed22a9..70ea8900 100644 --- a/benchmark/primaite_benchmark.py +++ b/benchmark/primaite_benchmark.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import json import shutil from datetime import datetime diff --git a/benchmark/report.py b/benchmark/report.py index 4035ceca..c11528ab 100644 --- a/benchmark/report.py +++ b/benchmark/report.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import json import sys from datetime import datetime diff --git a/benchmark/utils.py b/benchmark/utils.py index 2e92d80d..f17c64b7 100644 --- a/benchmark/utils.py +++ b/benchmark/utils.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import platform from typing import Dict diff --git a/docs/_templates/custom-class-template.rst b/docs/_templates/custom-class-template.rst index 920158d5..71e992bc 100644 --- a/docs/_templates/custom-class-template.rst +++ b/docs/_templates/custom-class-template.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK .. Credit to https://github.com/JamesALeedham/Sphinx-Autosummary-Recursion for the custom templates. diff --git a/docs/_templates/custom-module-template.rst b/docs/_templates/custom-module-template.rst index 98627e43..3a2ced35 100644 --- a/docs/_templates/custom-module-template.rst +++ b/docs/_templates/custom-module-template.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK .. Credit to https://github.com/JamesALeedham/Sphinx-Autosummary-Recursion for the custom templates. diff --git a/docs/api.rst b/docs/api.rst index 977f9e87..eb7e4719 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -2,7 +2,7 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK .. DO NOT DELETE THIS FILE! It contains the all-important `.. autosummary::` directive with `:recursive:` option, without diff --git a/docs/conf.py b/docs/conf.py index 318829fd..60739499 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK # Configuration file for the Sphinx documentation builder. # # For the full list of built-in configuration values, see the documentation: diff --git a/docs/index.rst b/docs/index.rst index 2ba43162..42cc1d6d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK Welcome to PrimAITE's documentation ==================================== diff --git a/docs/source/action_masking.rst b/docs/source/action_masking.rst index 264ab254..dad6a484 100644 --- a/docs/source/action_masking.rst +++ b/docs/source/action_masking.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK Action Masking ************** diff --git a/docs/source/config.rst b/docs/source/config.rst index eb0b9906..0fa4a4d5 100644 --- a/docs/source/config.rst +++ b/docs/source/config.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK PrimAITE |VERSION| Configuration ******************************** diff --git a/docs/source/configuration/agents.rst b/docs/source/configuration/agents.rst index dece94c5..d11f7892 100644 --- a/docs/source/configuration/agents.rst +++ b/docs/source/configuration/agents.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK ``agents`` diff --git a/docs/source/configuration/game.rst b/docs/source/configuration/game.rst index 2048708c..b3c139b2 100644 --- a/docs/source/configuration/game.rst +++ b/docs/source/configuration/game.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK ``game`` diff --git a/docs/source/configuration/io_settings.rst b/docs/source/configuration/io_settings.rst index 1c9585c9..ab3a978e 100644 --- a/docs/source/configuration/io_settings.rst +++ b/docs/source/configuration/io_settings.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK ``io_settings`` diff --git a/docs/source/configuration/simulation.rst b/docs/source/configuration/simulation.rst index fa1d774a..0b2067d8 100644 --- a/docs/source/configuration/simulation.rst +++ b/docs/source/configuration/simulation.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK ``simulation`` diff --git a/docs/source/configuration/simulation/nodes/common/common.rst b/docs/source/configuration/simulation/nodes/common/common.rst index a0f2eb13..c45eccf6 100644 --- a/docs/source/configuration/simulation/nodes/common/common.rst +++ b/docs/source/configuration/simulation/nodes/common/common.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK .. _Node Attributes: diff --git a/docs/source/configuration/simulation/nodes/common/common_host_node_attributes.rst b/docs/source/configuration/simulation/nodes/common/common_host_node_attributes.rst index bb3b2a52..b717340e 100644 --- a/docs/source/configuration/simulation/nodes/common/common_host_node_attributes.rst +++ b/docs/source/configuration/simulation/nodes/common/common_host_node_attributes.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK .. _common_host_node_attributes: diff --git a/docs/source/configuration/simulation/nodes/common/common_network_node_attributes.rst b/docs/source/configuration/simulation/nodes/common/common_network_node_attributes.rst index d556e2dc..035c7e55 100644 --- a/docs/source/configuration/simulation/nodes/common/common_network_node_attributes.rst +++ b/docs/source/configuration/simulation/nodes/common/common_network_node_attributes.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK .. _common_network_node_attributes: diff --git a/docs/source/configuration/simulation/nodes/common/common_node_attributes.rst b/docs/source/configuration/simulation/nodes/common/common_node_attributes.rst index 6a95911f..542b817b 100644 --- a/docs/source/configuration/simulation/nodes/common/common_node_attributes.rst +++ b/docs/source/configuration/simulation/nodes/common/common_node_attributes.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK .. _common_node_attributes: diff --git a/docs/source/configuration/simulation/nodes/common/node_type_list.rst b/docs/source/configuration/simulation/nodes/common/node_type_list.rst index 1ec496d9..21181019 100644 --- a/docs/source/configuration/simulation/nodes/common/node_type_list.rst +++ b/docs/source/configuration/simulation/nodes/common/node_type_list.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK ``type`` -------- diff --git a/docs/source/configuration/simulation/nodes/computer.rst b/docs/source/configuration/simulation/nodes/computer.rst index 32e0b2b9..456d11a2 100644 --- a/docs/source/configuration/simulation/nodes/computer.rst +++ b/docs/source/configuration/simulation/nodes/computer.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK .. _computer_configuration: diff --git a/docs/source/configuration/simulation/nodes/firewall.rst b/docs/source/configuration/simulation/nodes/firewall.rst index 775ffabd..84b5c99e 100644 --- a/docs/source/configuration/simulation/nodes/firewall.rst +++ b/docs/source/configuration/simulation/nodes/firewall.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK .. _firewall_configuration: diff --git a/docs/source/configuration/simulation/nodes/network_examples.rst b/docs/source/configuration/simulation/nodes/network_examples.rst index 2a34a206..80e934e5 100644 --- a/docs/source/configuration/simulation/nodes/network_examples.rst +++ b/docs/source/configuration/simulation/nodes/network_examples.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK .. _network_examples: diff --git a/docs/source/configuration/simulation/nodes/router.rst b/docs/source/configuration/simulation/nodes/router.rst index ac9d6411..4b41784c 100644 --- a/docs/source/configuration/simulation/nodes/router.rst +++ b/docs/source/configuration/simulation/nodes/router.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK .. _router_configuration: diff --git a/docs/source/configuration/simulation/nodes/server.rst b/docs/source/configuration/simulation/nodes/server.rst index 92b33ca7..616efb38 100644 --- a/docs/source/configuration/simulation/nodes/server.rst +++ b/docs/source/configuration/simulation/nodes/server.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK .. _server_configuration: diff --git a/docs/source/configuration/simulation/nodes/switch.rst b/docs/source/configuration/simulation/nodes/switch.rst index 17cf76f9..d09f5ba7 100644 --- a/docs/source/configuration/simulation/nodes/switch.rst +++ b/docs/source/configuration/simulation/nodes/switch.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK .. _switch_configuration: diff --git a/docs/source/configuration/simulation/software/applications.rst b/docs/source/configuration/simulation/software/applications.rst index 8c590d53..9973a167 100644 --- a/docs/source/configuration/simulation/software/applications.rst +++ b/docs/source/configuration/simulation/software/applications.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK ``applications`` ---------------- diff --git a/docs/source/configuration/simulation/software/services.rst b/docs/source/configuration/simulation/software/services.rst index fafdf2e8..ec6bbba9 100644 --- a/docs/source/configuration/simulation/software/services.rst +++ b/docs/source/configuration/simulation/software/services.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK ``services`` ------------ diff --git a/docs/source/customising_scenarios.rst b/docs/source/customising_scenarios.rst index 092f306b..df7d4b1e 100644 --- a/docs/source/customising_scenarios.rst +++ b/docs/source/customising_scenarios.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK Customising Agents ****************** diff --git a/docs/source/dependencies.rst b/docs/source/dependencies.rst index 74f3cd14..e8be00d3 100644 --- a/docs/source/dependencies.rst +++ b/docs/source/dependencies.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK .. role:: raw-html(raw) :format: html diff --git a/docs/source/developer_tools.rst b/docs/source/developer_tools.rst index a66b7902..b3d81a27 100644 --- a/docs/source/developer_tools.rst +++ b/docs/source/developer_tools.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK .. _Developer Tools: diff --git a/docs/source/environment.rst b/docs/source/environment.rst index a282c09e..251b1090 100644 --- a/docs/source/environment.rst +++ b/docs/source/environment.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK RL Environments *************** diff --git a/docs/source/example_notebooks.rst b/docs/source/example_notebooks.rst index 920175c9..6caeae3d 100644 --- a/docs/source/example_notebooks.rst +++ b/docs/source/example_notebooks.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK .. _example jupyter notebooks: diff --git a/docs/source/game_layer.rst b/docs/source/game_layer.rst index 775c02b5..58a274d9 100644 --- a/docs/source/game_layer.rst +++ b/docs/source/game_layer.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK PrimAITE Game layer ******************* diff --git a/docs/source/getting_started.rst b/docs/source/getting_started.rst index ded92c60..427d1823 100644 --- a/docs/source/getting_started.rst +++ b/docs/source/getting_started.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK .. _getting-started: diff --git a/docs/source/glossary.rst b/docs/source/glossary.rst index 8fff0ea3..02c578d1 100644 --- a/docs/source/glossary.rst +++ b/docs/source/glossary.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK Glossary ============= diff --git a/docs/source/how_to_guides/extensible_actions.rst b/docs/source/how_to_guides/extensible_actions.rst index 6e44a905..1c44c2b2 100644 --- a/docs/source/how_to_guides/extensible_actions.rst +++ b/docs/source/how_to_guides/extensible_actions.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK .. _about: diff --git a/docs/source/node_sets.rst b/docs/source/node_sets.rst index 866f0139..3c247478 100644 --- a/docs/source/node_sets.rst +++ b/docs/source/node_sets.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK .. _network_node_adder: diff --git a/docs/source/notebooks/executed_notebooks.rst b/docs/source/notebooks/executed_notebooks.rst index 3431d344..f4acfad6 100644 --- a/docs/source/notebooks/executed_notebooks.rst +++ b/docs/source/notebooks/executed_notebooks.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK .. _Executed Notebooks: diff --git a/docs/source/primaite-dependencies.rst b/docs/source/primaite-dependencies.rst index 8367ee61..14a96349 100644 --- a/docs/source/primaite-dependencies.rst +++ b/docs/source/primaite-dependencies.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +-------------------+---------+------------------------------------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ | Name | Version | License | Description | URL | diff --git a/docs/source/request_system.rst b/docs/source/request_system.rst index f2d2e68d..b89d0906 100644 --- a/docs/source/request_system.rst +++ b/docs/source/request_system.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK Request System ************** diff --git a/docs/source/rewards.rst b/docs/source/rewards.rst index 0163284c..254237ee 100644 --- a/docs/source/rewards.rst +++ b/docs/source/rewards.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK Rewards ####### diff --git a/docs/source/simulation.rst b/docs/source/simulation.rst index cc723e40..95807703 100644 --- a/docs/source/simulation.rst +++ b/docs/source/simulation.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK Simulation diff --git a/docs/source/simulation_components/network/airspace.rst b/docs/source/simulation_components/network/airspace.rst index 06a884a7..a6967b91 100644 --- a/docs/source/simulation_components/network/airspace.rst +++ b/docs/source/simulation_components/network/airspace.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK .. _airspace: diff --git a/docs/source/simulation_components/network/base_hardware.rst b/docs/source/simulation_components/network/base_hardware.rst index ce1e5c74..8b325ffc 100644 --- a/docs/source/simulation_components/network/base_hardware.rst +++ b/docs/source/simulation_components/network/base_hardware.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK ############# Base Hardware diff --git a/docs/source/simulation_components/network/network.rst b/docs/source/simulation_components/network/network.rst index 4cc121a3..152b74b8 100644 --- a/docs/source/simulation_components/network/network.rst +++ b/docs/source/simulation_components/network/network.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK .. _network: diff --git a/docs/source/simulation_components/network/network_interfaces.rst b/docs/source/simulation_components/network/network_interfaces.rst index c6b97a8e..663af7ba 100644 --- a/docs/source/simulation_components/network/network_interfaces.rst +++ b/docs/source/simulation_components/network/network_interfaces.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK ################################# Network Interface Hierarchy Model diff --git a/docs/source/simulation_components/network/nodes/firewall.rst b/docs/source/simulation_components/network/nodes/firewall.rst index 1ef16d63..f2d7e61a 100644 --- a/docs/source/simulation_components/network/nodes/firewall.rst +++ b/docs/source/simulation_components/network/nodes/firewall.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK ######## Firewall diff --git a/docs/source/simulation_components/network/nodes/host_node.rst b/docs/source/simulation_components/network/nodes/host_node.rst index b8aae098..2c1e75d0 100644 --- a/docs/source/simulation_components/network/nodes/host_node.rst +++ b/docs/source/simulation_components/network/nodes/host_node.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK ######### diff --git a/docs/source/simulation_components/network/nodes/network_node.rst b/docs/source/simulation_components/network/nodes/network_node.rst index e1fa976c..4aebe09f 100644 --- a/docs/source/simulation_components/network/nodes/network_node.rst +++ b/docs/source/simulation_components/network/nodes/network_node.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK ############ Network Node diff --git a/docs/source/simulation_components/network/nodes/router.rst b/docs/source/simulation_components/network/nodes/router.rst index 5d3de60f..fb582b23 100644 --- a/docs/source/simulation_components/network/nodes/router.rst +++ b/docs/source/simulation_components/network/nodes/router.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK ###### Router diff --git a/docs/source/simulation_components/network/nodes/switch.rst b/docs/source/simulation_components/network/nodes/switch.rst index 0ecbcbf3..e7143f0c 100644 --- a/docs/source/simulation_components/network/nodes/switch.rst +++ b/docs/source/simulation_components/network/nodes/switch.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK ###### Switch diff --git a/docs/source/simulation_components/network/nodes/wireless_router.rst b/docs/source/simulation_components/network/nodes/wireless_router.rst index c0c245b2..d7207846 100644 --- a/docs/source/simulation_components/network/nodes/wireless_router.rst +++ b/docs/source/simulation_components/network/nodes/wireless_router.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK ###### Wireless Router diff --git a/docs/source/simulation_components/network/transport_to_data_link_layer.rst b/docs/source/simulation_components/network/transport_to_data_link_layer.rst index 02bfdcdc..54118c90 100644 --- a/docs/source/simulation_components/network/transport_to_data_link_layer.rst +++ b/docs/source/simulation_components/network/transport_to_data_link_layer.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK Transport Layer to Data Link Layer ================================== diff --git a/docs/source/simulation_components/system/applications/c2_suite.rst b/docs/source/simulation_components/system/applications/c2_suite.rst index d045949a..3dd2b4fc 100644 --- a/docs/source/simulation_components/system/applications/c2_suite.rst +++ b/docs/source/simulation_components/system/applications/c2_suite.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK .. _C2_Suite: diff --git a/docs/source/simulation_components/system/applications/data_manipulation_bot.rst b/docs/source/simulation_components/system/applications/data_manipulation_bot.rst index 1a387514..91c33ede 100644 --- a/docs/source/simulation_components/system/applications/data_manipulation_bot.rst +++ b/docs/source/simulation_components/system/applications/data_manipulation_bot.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK .. _DataManipulationBot: diff --git a/docs/source/simulation_components/system/applications/database_client.rst b/docs/source/simulation_components/system/applications/database_client.rst index 1fea78ab..75a396b5 100644 --- a/docs/source/simulation_components/system/applications/database_client.rst +++ b/docs/source/simulation_components/system/applications/database_client.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK .. _DatabaseClient: diff --git a/docs/source/simulation_components/system/applications/dos_bot.rst b/docs/source/simulation_components/system/applications/dos_bot.rst index 6ad45424..5c0ae86a 100644 --- a/docs/source/simulation_components/system/applications/dos_bot.rst +++ b/docs/source/simulation_components/system/applications/dos_bot.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK .. _DoSBot: diff --git a/docs/source/simulation_components/system/applications/nmap.rst b/docs/source/simulation_components/system/applications/nmap.rst index a5615a43..a82735c8 100644 --- a/docs/source/simulation_components/system/applications/nmap.rst +++ b/docs/source/simulation_components/system/applications/nmap.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK .. _NMAP: diff --git a/docs/source/simulation_components/system/applications/ransomware_script.rst b/docs/source/simulation_components/system/applications/ransomware_script.rst index 5bff6991..b79ca802 100644 --- a/docs/source/simulation_components/system/applications/ransomware_script.rst +++ b/docs/source/simulation_components/system/applications/ransomware_script.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK .. _RansomwareScript: diff --git a/docs/source/simulation_components/system/applications/web_browser.rst b/docs/source/simulation_components/system/applications/web_browser.rst index c56c450d..7062887b 100644 --- a/docs/source/simulation_components/system/applications/web_browser.rst +++ b/docs/source/simulation_components/system/applications/web_browser.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK .. _WebBrowser: diff --git a/docs/source/simulation_components/system/common/common_configuration.rst b/docs/source/simulation_components/system/common/common_configuration.rst index c53ac8b8..411fd529 100644 --- a/docs/source/simulation_components/system/common/common_configuration.rst +++ b/docs/source/simulation_components/system/common/common_configuration.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK .. _Common Configuration: diff --git a/docs/source/simulation_components/system/common/db_payload_list.rst b/docs/source/simulation_components/system/common/db_payload_list.rst index 0930f09d..89668665 100644 --- a/docs/source/simulation_components/system/common/db_payload_list.rst +++ b/docs/source/simulation_components/system/common/db_payload_list.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK .. _Database Payload List: diff --git a/docs/source/simulation_components/system/internal_frame_processing.rst b/docs/source/simulation_components/system/internal_frame_processing.rst index 65336f9b..f82dec13 100644 --- a/docs/source/simulation_components/system/internal_frame_processing.rst +++ b/docs/source/simulation_components/system/internal_frame_processing.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK .. _internal_frame_processing: diff --git a/docs/source/simulation_components/system/list_of_applications.rst b/docs/source/simulation_components/system/list_of_applications.rst index 94090d93..a7e05ea6 100644 --- a/docs/source/simulation_components/system/list_of_applications.rst +++ b/docs/source/simulation_components/system/list_of_applications.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK .. toctree:: :maxdepth: 1 diff --git a/docs/source/simulation_components/system/list_of_services.rst b/docs/source/simulation_components/system/list_of_services.rst index b6995647..2082ac6f 100644 --- a/docs/source/simulation_components/system/list_of_services.rst +++ b/docs/source/simulation_components/system/list_of_services.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK .. toctree:: :maxdepth: 1 diff --git a/docs/source/simulation_components/system/list_of_system_applications.rst b/docs/source/simulation_components/system/list_of_system_applications.rst index c8807ef0..0c66662f 100644 --- a/docs/source/simulation_components/system/list_of_system_applications.rst +++ b/docs/source/simulation_components/system/list_of_system_applications.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK ``system applications`` """"""""""""""""""""""" diff --git a/docs/source/simulation_components/system/list_of_system_services.rst b/docs/source/simulation_components/system/list_of_system_services.rst index 9b5c3265..01df4dc8 100644 --- a/docs/source/simulation_components/system/list_of_system_services.rst +++ b/docs/source/simulation_components/system/list_of_system_services.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK ``system services`` """"""""""""""""""" diff --git a/docs/source/simulation_components/system/pcap.rst b/docs/source/simulation_components/system/pcap.rst index 830c28bd..0da28a39 100644 --- a/docs/source/simulation_components/system/pcap.rst +++ b/docs/source/simulation_components/system/pcap.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK PCAP ==== diff --git a/docs/source/simulation_components/system/services/database_service.rst b/docs/source/simulation_components/system/services/database_service.rst index f3e800cd..b41c1097 100644 --- a/docs/source/simulation_components/system/services/database_service.rst +++ b/docs/source/simulation_components/system/services/database_service.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK .. _DatabaseService: diff --git a/docs/source/simulation_components/system/services/dns_client.rst b/docs/source/simulation_components/system/services/dns_client.rst index eca152f0..6475b4d4 100644 --- a/docs/source/simulation_components/system/services/dns_client.rst +++ b/docs/source/simulation_components/system/services/dns_client.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK .. _DNSClient: diff --git a/docs/source/simulation_components/system/services/dns_server.rst b/docs/source/simulation_components/system/services/dns_server.rst index 1e30b9bd..3d699048 100644 --- a/docs/source/simulation_components/system/services/dns_server.rst +++ b/docs/source/simulation_components/system/services/dns_server.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK .. _DNSServer: diff --git a/docs/source/simulation_components/system/services/ftp_client.rst b/docs/source/simulation_components/system/services/ftp_client.rst index f9c7b4ce..47566e5f 100644 --- a/docs/source/simulation_components/system/services/ftp_client.rst +++ b/docs/source/simulation_components/system/services/ftp_client.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK .. _FTPClient: diff --git a/docs/source/simulation_components/system/services/ftp_server.rst b/docs/source/simulation_components/system/services/ftp_server.rst index f52fa043..e4cada29 100644 --- a/docs/source/simulation_components/system/services/ftp_server.rst +++ b/docs/source/simulation_components/system/services/ftp_server.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK .. _FTPServer: diff --git a/docs/source/simulation_components/system/services/ntp_client.rst b/docs/source/simulation_components/system/services/ntp_client.rst index 7af831bf..fb965029 100644 --- a/docs/source/simulation_components/system/services/ntp_client.rst +++ b/docs/source/simulation_components/system/services/ntp_client.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK .. _NTPClient: diff --git a/docs/source/simulation_components/system/services/ntp_server.rst b/docs/source/simulation_components/system/services/ntp_server.rst index a09c8bdd..68fadca9 100644 --- a/docs/source/simulation_components/system/services/ntp_server.rst +++ b/docs/source/simulation_components/system/services/ntp_server.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK .. _NTPServer: diff --git a/docs/source/simulation_components/system/services/terminal.rst b/docs/source/simulation_components/system/services/terminal.rst index 6909786e..bc5cee48 100644 --- a/docs/source/simulation_components/system/services/terminal.rst +++ b/docs/source/simulation_components/system/services/terminal.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK .. _Terminal: diff --git a/docs/source/simulation_components/system/services/web_server.rst b/docs/source/simulation_components/system/services/web_server.rst index cec20a60..011aa00f 100644 --- a/docs/source/simulation_components/system/services/web_server.rst +++ b/docs/source/simulation_components/system/services/web_server.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK .. _WebServer: diff --git a/docs/source/simulation_components/system/session_and_software_manager.rst b/docs/source/simulation_components/system/session_and_software_manager.rst index 230f6687..f20af556 100644 --- a/docs/source/simulation_components/system/session_and_software_manager.rst +++ b/docs/source/simulation_components/system/session_and_software_manager.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK Session and Software Manager ============================ diff --git a/docs/source/simulation_components/system/software.rst b/docs/source/simulation_components/system/software.rst index c8f0e2d3..d28815bb 100644 --- a/docs/source/simulation_components/system/software.rst +++ b/docs/source/simulation_components/system/software.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK .. _software: diff --git a/docs/source/simulation_components/system/sys_log.rst b/docs/source/simulation_components/system/sys_log.rst index cdf19faa..05629993 100644 --- a/docs/source/simulation_components/system/sys_log.rst +++ b/docs/source/simulation_components/system/sys_log.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK SysLog ====== diff --git a/docs/source/simulation_structure.rst b/docs/source/simulation_structure.rst index cd9ac409..7debe112 100644 --- a/docs/source/simulation_structure.rst +++ b/docs/source/simulation_structure.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK Simulation Structure diff --git a/docs/source/state_system.rst b/docs/source/state_system.rst index e31474ea..a5fd1df1 100644 --- a/docs/source/state_system.rst +++ b/docs/source/state_system.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK Simulation State ================ diff --git a/docs/source/varying_config_files.rst b/docs/source/varying_config_files.rst index fa66f0d9..942e522b 100644 --- a/docs/source/varying_config_files.rst +++ b/docs/source/varying_config_files.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK Defining variations in the config files ======================================= diff --git a/src/primaite/__init__.py b/src/primaite/__init__.py index 8dd84428..54eac69d 100644 --- a/src/primaite/__init__.py +++ b/src/primaite/__init__.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import datetime as datetime import logging import logging.config diff --git a/src/primaite/_legacy/actions.py b/src/primaite/_legacy/actions.py index 0eda7d86..d2457a20 100644 --- a/src/primaite/_legacy/actions.py +++ b/src/primaite/_legacy/actions.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK """ This module contains the ActionManager class which belongs to the Agent class. diff --git a/src/primaite/cli.py b/src/primaite/cli.py index 4fbbdec9..2bd18baf 100644 --- a/src/primaite/cli.py +++ b/src/primaite/cli.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK """Provides a CLI using Typer as an entry point.""" import logging import os diff --git a/src/primaite/config/__init__.py b/src/primaite/config/__init__.py index c2ae1b5b..7b5e2889 100644 --- a/src/primaite/config/__init__.py +++ b/src/primaite/config/__init__.py @@ -1,2 +1,2 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK """Configuration parameters for running experiments.""" diff --git a/src/primaite/config/load.py b/src/primaite/config/load.py index 39040d76..3553f527 100644 --- a/src/primaite/config/load.py +++ b/src/primaite/config/load.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from pathlib import Path from typing import Dict, Final, Union diff --git a/src/primaite/exceptions.py b/src/primaite/exceptions.py index afc55271..4487111d 100644 --- a/src/primaite/exceptions.py +++ b/src/primaite/exceptions.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK class PrimaiteError(Exception): """The root PrimAITE Error.""" diff --git a/src/primaite/game/__init__.py b/src/primaite/game/__init__.py index 39034e92..57f96a56 100644 --- a/src/primaite/game/__init__.py +++ b/src/primaite/game/__init__.py @@ -1,2 +1,2 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK """PrimAITE Game Layer.""" diff --git a/src/primaite/game/agent/__init__.py b/src/primaite/game/agent/__init__.py index be6c00e7..836b79af 100644 --- a/src/primaite/game/agent/__init__.py +++ b/src/primaite/game/agent/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/src/primaite/game/agent/actions/__init__.py b/src/primaite/game/agent/actions/__init__.py index 016a09ba..1100e125 100644 --- a/src/primaite/game/agent/actions/__init__.py +++ b/src/primaite/game/agent/actions/__init__.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from primaite.game.agent.actions import ( abstract, diff --git a/src/primaite/game/agent/actions/abstract.py b/src/primaite/game/agent/actions/abstract.py index 8c332d5e..15c9b4cb 100644 --- a/src/primaite/game/agent/actions/abstract.py +++ b/src/primaite/game/agent/actions/abstract.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from __future__ import annotations from abc import ABC diff --git a/src/primaite/game/agent/actions/acl.py b/src/primaite/game/agent/actions/acl.py index d2846ddb..6fefeeda 100644 --- a/src/primaite/game/agent/actions/acl.py +++ b/src/primaite/game/agent/actions/acl.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from __future__ import annotations from ipaddress import IPv4Address diff --git a/src/primaite/game/agent/actions/application.py b/src/primaite/game/agent/actions/application.py index 91e34eae..96609f93 100644 --- a/src/primaite/game/agent/actions/application.py +++ b/src/primaite/game/agent/actions/application.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import ClassVar from primaite.game.agent.actions.abstract import AbstractAction diff --git a/src/primaite/game/agent/actions/config.py b/src/primaite/game/agent/actions/config.py index 050e9b94..760e8dfa 100644 --- a/src/primaite/game/agent/actions/config.py +++ b/src/primaite/game/agent/actions/config.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import List, Optional, Union diff --git a/src/primaite/game/agent/actions/file.py b/src/primaite/game/agent/actions/file.py index b5e47c8a..e5ca1c46 100644 --- a/src/primaite/game/agent/actions/file.py +++ b/src/primaite/game/agent/actions/file.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import ClassVar from primaite.game.agent.actions.manager import AbstractAction diff --git a/src/primaite/game/agent/actions/folder.py b/src/primaite/game/agent/actions/folder.py index a27ca89b..d1fd5ef1 100644 --- a/src/primaite/game/agent/actions/folder.py +++ b/src/primaite/game/agent/actions/folder.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import ClassVar from primaite.game.agent.actions.manager import AbstractAction diff --git a/src/primaite/game/agent/actions/host_nic.py b/src/primaite/game/agent/actions/host_nic.py index e2adf7d7..7b290103 100644 --- a/src/primaite/game/agent/actions/host_nic.py +++ b/src/primaite/game/agent/actions/host_nic.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import ClassVar from primaite.game.agent.actions.manager import AbstractAction diff --git a/src/primaite/game/agent/actions/manager.py b/src/primaite/game/agent/actions/manager.py index a6a4f5a6..625d8cec 100644 --- a/src/primaite/game/agent/actions/manager.py +++ b/src/primaite/game/agent/actions/manager.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK """yaml example. agents: @@ -80,8 +80,6 @@ class ActionManager: self.action_map = {i: (a["action"], a["options"]) for i, a in act_map.items()} # make sure all numbers between 0 and N are represented as dict keys in action map assert all([i in self.action_map.keys() for i in range(len(self.action_map))]) - self.node_names: List[str] = [n["node_name"] for n in nodes] - """List of node names in this action space. The list order is the mapping between node index and node name.""" def get_action(self, action: int) -> Tuple[str, Dict]: """Produce action in CAOS format.""" diff --git a/src/primaite/game/agent/actions/network.py b/src/primaite/game/agent/actions/network.py index 346da9b7..fa1c4451 100644 --- a/src/primaite/game/agent/actions/network.py +++ b/src/primaite/game/agent/actions/network.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import ClassVar diff --git a/src/primaite/game/agent/actions/node.py b/src/primaite/game/agent/actions/node.py index 480cb8da..c6b74f2e 100644 --- a/src/primaite/game/agent/actions/node.py +++ b/src/primaite/game/agent/actions/node.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from abc import abstractmethod from typing import ClassVar, List, Optional, Union diff --git a/src/primaite/game/agent/actions/service.py b/src/primaite/game/agent/actions/service.py index 7ccffb0a..fa47ffb1 100644 --- a/src/primaite/game/agent/actions/service.py +++ b/src/primaite/game/agent/actions/service.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import ClassVar from primaite.game.agent.actions.manager import AbstractAction diff --git a/src/primaite/game/agent/actions/session.py b/src/primaite/game/agent/actions/session.py index a0805a49..1191987b 100644 --- a/src/primaite/game/agent/actions/session.py +++ b/src/primaite/game/agent/actions/session.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from abc import abstractmethod from primaite.game.agent.actions.manager import AbstractAction diff --git a/src/primaite/game/agent/agent_log.py b/src/primaite/game/agent/agent_log.py index 62ef4884..59fb4702 100644 --- a/src/primaite/game/agent/agent_log.py +++ b/src/primaite/game/agent/agent_log.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import logging from pathlib import Path diff --git a/src/primaite/game/agent/interface.py b/src/primaite/game/agent/interface.py index 14b97821..4acc9108 100644 --- a/src/primaite/game/agent/interface.py +++ b/src/primaite/game/agent/interface.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK """Interface for agents.""" from abc import ABC, abstractmethod from typing import Any, Dict, List, Optional, Tuple, TYPE_CHECKING diff --git a/src/primaite/game/agent/observations/__init__.py b/src/primaite/game/agent/observations/__init__.py index c4811c98..a38095b3 100644 --- a/src/primaite/game/agent/observations/__init__.py +++ b/src/primaite/game/agent/observations/__init__.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK # flake8: noqa # Pre-import all the observations when we load up the observations module so that they can be resolved by the parser. from primaite.game.agent.observations.acl_observation import ACLObservation diff --git a/src/primaite/game/agent/observations/acl_observation.py b/src/primaite/game/agent/observations/acl_observation.py index 41af5a8f..86a6463a 100644 --- a/src/primaite/game/agent/observations/acl_observation.py +++ b/src/primaite/game/agent/observations/acl_observation.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from __future__ import annotations from ipaddress import IPv4Address diff --git a/src/primaite/game/agent/observations/file_system_observations.py b/src/primaite/game/agent/observations/file_system_observations.py index 1c73d026..50ca93fd 100644 --- a/src/primaite/game/agent/observations/file_system_observations.py +++ b/src/primaite/game/agent/observations/file_system_observations.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from __future__ import annotations from typing import Dict, Iterable, List, Optional diff --git a/src/primaite/game/agent/observations/firewall_observation.py b/src/primaite/game/agent/observations/firewall_observation.py index 42ceaff0..a89ddfc5 100644 --- a/src/primaite/game/agent/observations/firewall_observation.py +++ b/src/primaite/game/agent/observations/firewall_observation.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from __future__ import annotations from typing import Dict, List, Optional diff --git a/src/primaite/game/agent/observations/host_observations.py b/src/primaite/game/agent/observations/host_observations.py index 617e8eee..03e9aca1 100644 --- a/src/primaite/game/agent/observations/host_observations.py +++ b/src/primaite/game/agent/observations/host_observations.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from __future__ import annotations from typing import Dict, List, Optional diff --git a/src/primaite/game/agent/observations/link_observation.py b/src/primaite/game/agent/observations/link_observation.py index 9af39a22..851e9557 100644 --- a/src/primaite/game/agent/observations/link_observation.py +++ b/src/primaite/game/agent/observations/link_observation.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from __future__ import annotations from typing import Any, Dict, List diff --git a/src/primaite/game/agent/observations/nic_observations.py b/src/primaite/game/agent/observations/nic_observations.py index d180b641..f87d2d76 100644 --- a/src/primaite/game/agent/observations/nic_observations.py +++ b/src/primaite/game/agent/observations/nic_observations.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from __future__ import annotations from typing import Dict, List, Optional diff --git a/src/primaite/game/agent/observations/node_observations.py b/src/primaite/game/agent/observations/node_observations.py index e11521b6..03869367 100644 --- a/src/primaite/game/agent/observations/node_observations.py +++ b/src/primaite/game/agent/observations/node_observations.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from __future__ import annotations from typing import Dict, List, Optional diff --git a/src/primaite/game/agent/observations/observation_manager.py b/src/primaite/game/agent/observations/observation_manager.py index 9b20fdcb..71a60433 100644 --- a/src/primaite/game/agent/observations/observation_manager.py +++ b/src/primaite/game/agent/observations/observation_manager.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from __future__ import annotations from typing import Any, Dict, List, Optional diff --git a/src/primaite/game/agent/observations/observations.py b/src/primaite/game/agent/observations/observations.py index a9663c56..49b9ab72 100644 --- a/src/primaite/game/agent/observations/observations.py +++ b/src/primaite/game/agent/observations/observations.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK """Manages the observation space for the agent.""" from abc import ABC, abstractmethod from typing import Any, Dict, Iterable, Optional, Type, Union diff --git a/src/primaite/game/agent/observations/router_observation.py b/src/primaite/game/agent/observations/router_observation.py index d064936a..ca455f4c 100644 --- a/src/primaite/game/agent/observations/router_observation.py +++ b/src/primaite/game/agent/observations/router_observation.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from __future__ import annotations from typing import Dict, List, Optional diff --git a/src/primaite/game/agent/observations/software_observation.py b/src/primaite/game/agent/observations/software_observation.py index 15cd2447..37810c6e 100644 --- a/src/primaite/game/agent/observations/software_observation.py +++ b/src/primaite/game/agent/observations/software_observation.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from __future__ import annotations from typing import Dict diff --git a/src/primaite/game/agent/rewards.py b/src/primaite/game/agent/rewards.py index f528c851..fead27f2 100644 --- a/src/primaite/game/agent/rewards.py +++ b/src/primaite/game/agent/rewards.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK """ Manages the reward function for the agent. diff --git a/src/primaite/game/agent/scripted_agents/__init__.py b/src/primaite/game/agent/scripted_agents/__init__.py index be6c00e7..836b79af 100644 --- a/src/primaite/game/agent/scripted_agents/__init__.py +++ b/src/primaite/game/agent/scripted_agents/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/src/primaite/game/agent/scripted_agents/data_manipulation_bot.py b/src/primaite/game/agent/scripted_agents/data_manipulation_bot.py index eb0ce957..2432dd7b 100644 --- a/src/primaite/game/agent/scripted_agents/data_manipulation_bot.py +++ b/src/primaite/game/agent/scripted_agents/data_manipulation_bot.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import random from typing import Dict, Tuple diff --git a/src/primaite/game/agent/scripted_agents/probabilistic_agent.py b/src/primaite/game/agent/scripted_agents/probabilistic_agent.py index cd44644f..ce4d90d1 100644 --- a/src/primaite/game/agent/scripted_agents/probabilistic_agent.py +++ b/src/primaite/game/agent/scripted_agents/probabilistic_agent.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK """Agents with predefined behaviours.""" from typing import Dict, Optional, Tuple diff --git a/src/primaite/game/agent/scripted_agents/random_agent.py b/src/primaite/game/agent/scripted_agents/random_agent.py index eade3a0c..2c2ff091 100644 --- a/src/primaite/game/agent/scripted_agents/random_agent.py +++ b/src/primaite/game/agent/scripted_agents/random_agent.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import random from typing import Dict, Optional, Tuple diff --git a/src/primaite/game/agent/scripted_agents/tap001.py b/src/primaite/game/agent/scripted_agents/tap001.py index 6d370654..1ed200d7 100644 --- a/src/primaite/game/agent/scripted_agents/tap001.py +++ b/src/primaite/game/agent/scripted_agents/tap001.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import random from typing import Dict, Tuple diff --git a/src/primaite/game/agent/utils.py b/src/primaite/game/agent/utils.py index 15efd0b6..87b02858 100644 --- a/src/primaite/game/agent/utils.py +++ b/src/primaite/game/agent/utils.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import Any, Dict, Hashable, Optional, Sequence NOT_PRESENT_IN_STATE = object() diff --git a/src/primaite/game/game.py b/src/primaite/game/game.py index c8fbac4e..6555e272 100644 --- a/src/primaite/game/game.py +++ b/src/primaite/game/game.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK """PrimAITE game - Encapsulates the simulation and agents.""" from ipaddress import IPv4Address from typing import Dict, List, Optional, Union diff --git a/src/primaite/game/science.py b/src/primaite/game/science.py index 8d8949df..2cb5de7d 100644 --- a/src/primaite/game/science.py +++ b/src/primaite/game/science.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from random import random from typing import Any, Iterable, Mapping diff --git a/src/primaite/interface/__init__.py b/src/primaite/interface/__init__.py index be6c00e7..836b79af 100644 --- a/src/primaite/interface/__init__.py +++ b/src/primaite/interface/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/src/primaite/interface/request.py b/src/primaite/interface/request.py index 1a9f0e5f..03d6491e 100644 --- a/src/primaite/interface/request.py +++ b/src/primaite/interface/request.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import Dict, ForwardRef, List, Literal, Union from pydantic import BaseModel, ConfigDict, StrictBool # , validate_call diff --git a/src/primaite/session/__init__.py b/src/primaite/session/__init__.py index be6c00e7..836b79af 100644 --- a/src/primaite/session/__init__.py +++ b/src/primaite/session/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/src/primaite/session/environment.py b/src/primaite/session/environment.py index c66663e3..8e608ede 100644 --- a/src/primaite/session/environment.py +++ b/src/primaite/session/environment.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import json import random import sys diff --git a/src/primaite/session/episode_schedule.py b/src/primaite/session/episode_schedule.py index ad4d38e9..126dcf9f 100644 --- a/src/primaite/session/episode_schedule.py +++ b/src/primaite/session/episode_schedule.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import 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 78d7cb3c..6c2f4f29 100644 --- a/src/primaite/session/io.py +++ b/src/primaite/session/io.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import json from datetime import datetime from pathlib import Path diff --git a/src/primaite/session/ray_envs.py b/src/primaite/session/ray_envs.py index 33c74b0e..33ba0540 100644 --- a/src/primaite/session/ray_envs.py +++ b/src/primaite/session/ray_envs.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import json from typing import Dict, SupportsFloat, Tuple diff --git a/src/primaite/setup/__init__.py b/src/primaite/setup/__init__.py index 12e7c4e7..1447a47b 100644 --- a/src/primaite/setup/__init__.py +++ b/src/primaite/setup/__init__.py @@ -1,2 +1,2 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK """Utilities to prepare the user's data folders.""" diff --git a/src/primaite/setup/reset_demo_notebooks.py b/src/primaite/setup/reset_demo_notebooks.py index f17fb211..ad4091e3 100644 --- a/src/primaite/setup/reset_demo_notebooks.py +++ b/src/primaite/setup/reset_demo_notebooks.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import 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 c7eeecd5..a94d6d4a 100644 --- a/src/primaite/setup/reset_example_configs.py +++ b/src/primaite/setup/reset_example_configs.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import filecmp import os import shutil diff --git a/src/primaite/simulator/__init__.py b/src/primaite/simulator/__init__.py index ade1a73b..e85a2d1e 100644 --- a/src/primaite/simulator/__init__.py +++ b/src/primaite/simulator/__init__.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK """Warning: SIM_OUTPUT is a mutable global variable for the simulation output directory.""" from datetime import datetime from enum import IntEnum diff --git a/src/primaite/simulator/core.py b/src/primaite/simulator/core.py index 848570fe..567a0493 100644 --- a/src/primaite/simulator/core.py +++ b/src/primaite/simulator/core.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK # flake8: noqa """Core of the PrimAITE Simulator.""" import warnings diff --git a/src/primaite/simulator/domain/__init__.py b/src/primaite/simulator/domain/__init__.py index be6c00e7..836b79af 100644 --- a/src/primaite/simulator/domain/__init__.py +++ b/src/primaite/simulator/domain/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/domain/account.py b/src/primaite/simulator/domain/account.py index d955cf55..85ec6d46 100644 --- a/src/primaite/simulator/domain/account.py +++ b/src/primaite/simulator/domain/account.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK """User account simulation.""" from enum import Enum from typing import Dict diff --git a/src/primaite/simulator/domain/controller.py b/src/primaite/simulator/domain/controller.py index a264ba24..d8b7782c 100644 --- a/src/primaite/simulator/domain/controller.py +++ b/src/primaite/simulator/domain/controller.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from enum import Enum from typing import Dict, Final, List, Literal, Tuple diff --git a/src/primaite/simulator/file_system/__init__.py b/src/primaite/simulator/file_system/__init__.py index be6c00e7..836b79af 100644 --- a/src/primaite/simulator/file_system/__init__.py +++ b/src/primaite/simulator/file_system/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/file_system/file.py b/src/primaite/simulator/file_system/file.py index ba39c791..57d01ec9 100644 --- a/src/primaite/simulator/file_system/file.py +++ b/src/primaite/simulator/file_system/file.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from __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 2162915f..8ff4b6fb 100644 --- a/src/primaite/simulator/file_system/file_system.py +++ b/src/primaite/simulator/file_system/file_system.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from __future__ import annotations from pathlib import Path diff --git a/src/primaite/simulator/file_system/file_system_item_abc.py b/src/primaite/simulator/file_system/file_system_item_abc.py index a9db8825..48b95d20 100644 --- a/src/primaite/simulator/file_system/file_system_item_abc.py +++ b/src/primaite/simulator/file_system/file_system_item_abc.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from __future__ import annotations import math diff --git a/src/primaite/simulator/file_system/file_type.py b/src/primaite/simulator/file_system/file_type.py index e6e81070..343d3565 100644 --- a/src/primaite/simulator/file_system/file_type.py +++ b/src/primaite/simulator/file_system/file_type.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from __future__ import annotations from enum import Enum diff --git a/src/primaite/simulator/file_system/folder.py b/src/primaite/simulator/file_system/folder.py index c98e4492..ee0f3d01 100644 --- a/src/primaite/simulator/file_system/folder.py +++ b/src/primaite/simulator/file_system/folder.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from __future__ import annotations import warnings diff --git a/src/primaite/simulator/network/__init__.py b/src/primaite/simulator/network/__init__.py index be6c00e7..836b79af 100644 --- a/src/primaite/simulator/network/__init__.py +++ b/src/primaite/simulator/network/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/network/airspace.py b/src/primaite/simulator/network/airspace.py index 2b8503d6..1f6fe6b0 100644 --- a/src/primaite/simulator/network/airspace.py +++ b/src/primaite/simulator/network/airspace.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from __future__ import annotations from abc import ABC, abstractmethod diff --git a/src/primaite/simulator/network/container.py b/src/primaite/simulator/network/container.py index 1082e172..bf677d5c 100644 --- a/src/primaite/simulator/network/container.py +++ b/src/primaite/simulator/network/container.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address from typing import Any, Dict, List, Optional diff --git a/src/primaite/simulator/network/creation.py b/src/primaite/simulator/network/creation.py index 5d36f58b..94c45428 100644 --- a/src/primaite/simulator/network/creation.py +++ b/src/primaite/simulator/network/creation.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from abc import ABC, abstractmethod from ipaddress import IPv4Address from typing import Any, ClassVar, Dict, Literal, Type diff --git a/src/primaite/simulator/network/hardware/__init__.py b/src/primaite/simulator/network/hardware/__init__.py index be6c00e7..836b79af 100644 --- a/src/primaite/simulator/network/hardware/__init__.py +++ b/src/primaite/simulator/network/hardware/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/network/hardware/base.py b/src/primaite/simulator/network/hardware/base.py index 51e200e7..8324715f 100644 --- a/src/primaite/simulator/network/hardware/base.py +++ b/src/primaite/simulator/network/hardware/base.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from __future__ import annotations import re diff --git a/src/primaite/simulator/network/hardware/network_interface/__init__.py b/src/primaite/simulator/network/hardware/network_interface/__init__.py index be6c00e7..836b79af 100644 --- a/src/primaite/simulator/network/hardware/network_interface/__init__.py +++ b/src/primaite/simulator/network/hardware/network_interface/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/network/hardware/network_interface/wireless/__init__.py b/src/primaite/simulator/network/hardware/network_interface/wireless/__init__.py index be6c00e7..836b79af 100644 --- a/src/primaite/simulator/network/hardware/network_interface/wireless/__init__.py +++ b/src/primaite/simulator/network/hardware/network_interface/wireless/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/network/hardware/network_interface/wireless/wireless_access_point.py b/src/primaite/simulator/network/hardware/network_interface/wireless/wireless_access_point.py index a9a31768..3997872c 100644 --- a/src/primaite/simulator/network/hardware/network_interface/wireless/wireless_access_point.py +++ b/src/primaite/simulator/network/hardware/network_interface/wireless/wireless_access_point.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import Dict from primaite.simulator.network.hardware.base import ( diff --git a/src/primaite/simulator/network/hardware/network_interface/wireless/wireless_nic.py b/src/primaite/simulator/network/hardware/network_interface/wireless/wireless_nic.py index eebaedc5..9bc4cd6f 100644 --- a/src/primaite/simulator/network/hardware/network_interface/wireless/wireless_nic.py +++ b/src/primaite/simulator/network/hardware/network_interface/wireless/wireless_nic.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import Dict from primaite.simulator.network.hardware.base import ( diff --git a/src/primaite/simulator/network/hardware/node_operating_state.py b/src/primaite/simulator/network/hardware/node_operating_state.py index e64ef08b..8771cb84 100644 --- a/src/primaite/simulator/network/hardware/node_operating_state.py +++ b/src/primaite/simulator/network/hardware/node_operating_state.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from enum import Enum diff --git a/src/primaite/simulator/network/hardware/nodes/__init__.py b/src/primaite/simulator/network/hardware/nodes/__init__.py index be6c00e7..836b79af 100644 --- a/src/primaite/simulator/network/hardware/nodes/__init__.py +++ b/src/primaite/simulator/network/hardware/nodes/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/network/hardware/nodes/host/__init__.py b/src/primaite/simulator/network/hardware/nodes/host/__init__.py index be6c00e7..836b79af 100644 --- a/src/primaite/simulator/network/hardware/nodes/host/__init__.py +++ b/src/primaite/simulator/network/hardware/nodes/host/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/network/hardware/nodes/host/computer.py b/src/primaite/simulator/network/hardware/nodes/host/computer.py index 4253d15c..11b925b9 100644 --- a/src/primaite/simulator/network/hardware/nodes/host/computer.py +++ b/src/primaite/simulator/network/hardware/nodes/host/computer.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import ClassVar, Dict from primaite.simulator.network.hardware.nodes.host.host_node import HostNode diff --git a/src/primaite/simulator/network/hardware/nodes/host/host_node.py b/src/primaite/simulator/network/hardware/nodes/host/host_node.py index 0c309136..c51afbca 100644 --- a/src/primaite/simulator/network/hardware/nodes/host/host_node.py +++ b/src/primaite/simulator/network/hardware/nodes/host/host_node.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from __future__ import annotations from ipaddress import IPv4Address diff --git a/src/primaite/simulator/network/hardware/nodes/host/server.py b/src/primaite/simulator/network/hardware/nodes/host/server.py index bf1ef39b..e16cfd8f 100644 --- a/src/primaite/simulator/network/hardware/nodes/host/server.py +++ b/src/primaite/simulator/network/hardware/nodes/host/server.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from primaite.simulator.network.hardware.nodes.host.host_node import HostNode diff --git a/src/primaite/simulator/network/hardware/nodes/network/__init__.py b/src/primaite/simulator/network/hardware/nodes/network/__init__.py index be6c00e7..836b79af 100644 --- a/src/primaite/simulator/network/hardware/nodes/network/__init__.py +++ b/src/primaite/simulator/network/hardware/nodes/network/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/network/hardware/nodes/network/firewall.py b/src/primaite/simulator/network/hardware/nodes/network/firewall.py index 84cf8530..f1ca4930 100644 --- a/src/primaite/simulator/network/hardware/nodes/network/firewall.py +++ b/src/primaite/simulator/network/hardware/nodes/network/firewall.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address from typing import Dict, Final, Union diff --git a/src/primaite/simulator/network/hardware/nodes/network/network_node.py b/src/primaite/simulator/network/hardware/nodes/network/network_node.py index a5b8544f..22ff2b28 100644 --- a/src/primaite/simulator/network/hardware/nodes/network/network_node.py +++ b/src/primaite/simulator/network/hardware/nodes/network/network_node.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from abc import abstractmethod from typing import Optional diff --git a/src/primaite/simulator/network/hardware/nodes/network/router.py b/src/primaite/simulator/network/hardware/nodes/network/router.py index e921faff..4a049f99 100644 --- a/src/primaite/simulator/network/hardware/nodes/network/router.py +++ b/src/primaite/simulator/network/hardware/nodes/network/router.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from __future__ import annotations import secrets diff --git a/src/primaite/simulator/network/hardware/nodes/network/switch.py b/src/primaite/simulator/network/hardware/nodes/network/switch.py index d29152a4..db923f1a 100644 --- a/src/primaite/simulator/network/hardware/nodes/network/switch.py +++ b/src/primaite/simulator/network/hardware/nodes/network/switch.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from __future__ import annotations from typing import Dict, Optional diff --git a/src/primaite/simulator/network/hardware/nodes/network/wireless_router.py b/src/primaite/simulator/network/hardware/nodes/network/wireless_router.py index aed314d2..804a570e 100644 --- a/src/primaite/simulator/network/hardware/nodes/network/wireless_router.py +++ b/src/primaite/simulator/network/hardware/nodes/network/wireless_router.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address from typing import Any, Dict, Optional, Union diff --git a/src/primaite/simulator/network/networks.py b/src/primaite/simulator/network/networks.py index 2c3c15b4..c840748e 100644 --- a/src/primaite/simulator/network/networks.py +++ b/src/primaite/simulator/network/networks.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address import yaml diff --git a/src/primaite/simulator/network/nmne.py b/src/primaite/simulator/network/nmne.py index c9cff5de..a2e5f1fe 100644 --- a/src/primaite/simulator/network/nmne.py +++ b/src/primaite/simulator/network/nmne.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import List from pydantic import BaseModel, ConfigDict diff --git a/src/primaite/simulator/network/protocols/__init__.py b/src/primaite/simulator/network/protocols/__init__.py index be6c00e7..836b79af 100644 --- a/src/primaite/simulator/network/protocols/__init__.py +++ b/src/primaite/simulator/network/protocols/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/network/protocols/arp.py b/src/primaite/simulator/network/protocols/arp.py index 9e7f7ebe..86e461d0 100644 --- a/src/primaite/simulator/network/protocols/arp.py +++ b/src/primaite/simulator/network/protocols/arp.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from __future__ import annotations from ipaddress import IPv4Address diff --git a/src/primaite/simulator/network/protocols/dns.py b/src/primaite/simulator/network/protocols/dns.py index eb7b74ad..c0fed1aa 100644 --- a/src/primaite/simulator/network/protocols/dns.py +++ b/src/primaite/simulator/network/protocols/dns.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from __future__ import annotations from ipaddress import IPv4Address diff --git a/src/primaite/simulator/network/protocols/ftp.py b/src/primaite/simulator/network/protocols/ftp.py index c570a634..fd8fdd2b 100644 --- a/src/primaite/simulator/network/protocols/ftp.py +++ b/src/primaite/simulator/network/protocols/ftp.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from enum import Enum from typing import Any, Optional, Union diff --git a/src/primaite/simulator/network/protocols/http.py b/src/primaite/simulator/network/protocols/http.py index 5390cd26..54abdd98 100644 --- a/src/primaite/simulator/network/protocols/http.py +++ b/src/primaite/simulator/network/protocols/http.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from enum import Enum, IntEnum from primaite.simulator.network.protocols.packet import DataPacket diff --git a/src/primaite/simulator/network/protocols/icmp.py b/src/primaite/simulator/network/protocols/icmp.py index 9f0626f0..fcbe15da 100644 --- a/src/primaite/simulator/network/protocols/icmp.py +++ b/src/primaite/simulator/network/protocols/icmp.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import secrets from enum import Enum from typing import Union diff --git a/src/primaite/simulator/network/protocols/masquerade.py b/src/primaite/simulator/network/protocols/masquerade.py index 5c5f03b2..e0ed26b7 100644 --- a/src/primaite/simulator/network/protocols/masquerade.py +++ b/src/primaite/simulator/network/protocols/masquerade.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from enum import Enum from typing import Optional diff --git a/src/primaite/simulator/network/protocols/ntp.py b/src/primaite/simulator/network/protocols/ntp.py index 74e02dab..c9b6f877 100644 --- a/src/primaite/simulator/network/protocols/ntp.py +++ b/src/primaite/simulator/network/protocols/ntp.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from __future__ import annotations from datetime import datetime diff --git a/src/primaite/simulator/network/protocols/packet.py b/src/primaite/simulator/network/protocols/packet.py index 7eeec13b..6f28f716 100644 --- a/src/primaite/simulator/network/protocols/packet.py +++ b/src/primaite/simulator/network/protocols/packet.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import Any from pydantic import BaseModel diff --git a/src/primaite/simulator/network/protocols/ssh.py b/src/primaite/simulator/network/protocols/ssh.py index be7f842f..03411fb5 100644 --- a/src/primaite/simulator/network/protocols/ssh.py +++ b/src/primaite/simulator/network/protocols/ssh.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from enum import IntEnum from typing import Optional diff --git a/src/primaite/simulator/network/transmission/__init__.py b/src/primaite/simulator/network/transmission/__init__.py index be6c00e7..836b79af 100644 --- a/src/primaite/simulator/network/transmission/__init__.py +++ b/src/primaite/simulator/network/transmission/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/network/transmission/data_link_layer.py b/src/primaite/simulator/network/transmission/data_link_layer.py index 259d62e3..e7c2a124 100644 --- a/src/primaite/simulator/network/transmission/data_link_layer.py +++ b/src/primaite/simulator/network/transmission/data_link_layer.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from datetime import datetime from typing import Any, Optional diff --git a/src/primaite/simulator/network/transmission/network_layer.py b/src/primaite/simulator/network/transmission/network_layer.py index 49dcd1f5..7a6b34c9 100644 --- a/src/primaite/simulator/network/transmission/network_layer.py +++ b/src/primaite/simulator/network/transmission/network_layer.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from enum import Enum from pydantic import BaseModel diff --git a/src/primaite/simulator/network/transmission/primaite_layer.py b/src/primaite/simulator/network/transmission/primaite_layer.py index 981b6fbc..8ff4ac02 100644 --- a/src/primaite/simulator/network/transmission/primaite_layer.py +++ b/src/primaite/simulator/network/transmission/primaite_layer.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from enum import Enum from pydantic import BaseModel diff --git a/src/primaite/simulator/network/transmission/transport_layer.py b/src/primaite/simulator/network/transmission/transport_layer.py index 10cf802c..689eea2f 100644 --- a/src/primaite/simulator/network/transmission/transport_layer.py +++ b/src/primaite/simulator/network/transmission/transport_layer.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from enum import Enum from typing import List diff --git a/src/primaite/simulator/network/utils.py b/src/primaite/simulator/network/utils.py index 4fd1834a..b4d6c815 100644 --- a/src/primaite/simulator/network/utils.py +++ b/src/primaite/simulator/network/utils.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import Union diff --git a/src/primaite/simulator/sim_container.py b/src/primaite/simulator/sim_container.py index 809b52db..2a1deef4 100644 --- a/src/primaite/simulator/sim_container.py +++ b/src/primaite/simulator/sim_container.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from 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 be6c00e7..836b79af 100644 --- a/src/primaite/simulator/system/__init__.py +++ b/src/primaite/simulator/system/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/system/applications/__init__.py b/src/primaite/simulator/system/applications/__init__.py index be6c00e7..836b79af 100644 --- a/src/primaite/simulator/system/applications/__init__.py +++ b/src/primaite/simulator/system/applications/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/system/applications/application.py b/src/primaite/simulator/system/applications/application.py index a7871315..1752c09a 100644 --- a/src/primaite/simulator/system/applications/application.py +++ b/src/primaite/simulator/system/applications/application.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from __future__ import annotations from abc import abstractmethod diff --git a/src/primaite/simulator/system/applications/database_client.py b/src/primaite/simulator/system/applications/database_client.py index cd4b2a03..840214f3 100644 --- a/src/primaite/simulator/system/applications/database_client.py +++ b/src/primaite/simulator/system/applications/database_client.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from __future__ import annotations from ipaddress import IPv4Address diff --git a/src/primaite/simulator/system/applications/nmap.py b/src/primaite/simulator/system/applications/nmap.py index e2b9117d..f064eae3 100644 --- a/src/primaite/simulator/system/applications/nmap.py +++ b/src/primaite/simulator/system/applications/nmap.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address, IPv4Network from typing import Any, Dict, Final, List, Optional, Set, Tuple, Union diff --git a/src/primaite/simulator/system/applications/red_applications/__init__.py b/src/primaite/simulator/system/applications/red_applications/__init__.py index be6c00e7..836b79af 100644 --- a/src/primaite/simulator/system/applications/red_applications/__init__.py +++ b/src/primaite/simulator/system/applications/red_applications/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/system/applications/red_applications/c2/__init__.py b/src/primaite/simulator/system/applications/red_applications/c2/__init__.py index 60e39743..33cc555f 100644 --- a/src/primaite/simulator/system/applications/red_applications/c2/__init__.py +++ b/src/primaite/simulator/system/applications/red_applications/c2/__init__.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import Optional, Union from pydantic import BaseModel, Field, field_validator, ValidationInfo diff --git a/src/primaite/simulator/system/applications/red_applications/c2/abstract_c2.py b/src/primaite/simulator/system/applications/red_applications/c2/abstract_c2.py index f77bc33a..4cd54d69 100644 --- a/src/primaite/simulator/system/applications/red_applications/c2/abstract_c2.py +++ b/src/primaite/simulator/system/applications/red_applications/c2/abstract_c2.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from abc import abstractmethod from enum import Enum from ipaddress import IPv4Address diff --git a/src/primaite/simulator/system/applications/red_applications/c2/c2_beacon.py b/src/primaite/simulator/system/applications/red_applications/c2/c2_beacon.py index c0c3d872..b25eea6e 100644 --- a/src/primaite/simulator/system/applications/red_applications/c2/c2_beacon.py +++ b/src/primaite/simulator/system/applications/red_applications/c2/c2_beacon.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address from typing import Dict, Optional diff --git a/src/primaite/simulator/system/applications/red_applications/c2/c2_server.py b/src/primaite/simulator/system/applications/red_applications/c2/c2_server.py index f948d696..654b86e7 100644 --- a/src/primaite/simulator/system/applications/red_applications/c2/c2_server.py +++ b/src/primaite/simulator/system/applications/red_applications/c2/c2_server.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import Dict, Optional from prettytable import MARKDOWN, PrettyTable diff --git a/src/primaite/simulator/system/applications/red_applications/data_manipulation_bot.py b/src/primaite/simulator/system/applications/red_applications/data_manipulation_bot.py index 9fdbae57..0423087e 100644 --- a/src/primaite/simulator/system/applications/red_applications/data_manipulation_bot.py +++ b/src/primaite/simulator/system/applications/red_applications/data_manipulation_bot.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from enum import IntEnum from ipaddress import IPv4Address from typing import Dict, Optional 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 fb2c8847..99c4acb3 100644 --- a/src/primaite/simulator/system/applications/red_applications/dos_bot.py +++ b/src/primaite/simulator/system/applications/red_applications/dos_bot.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from enum import IntEnum from ipaddress import IPv4Address from typing import Dict, Optional diff --git a/src/primaite/simulator/system/applications/red_applications/ransomware_script.py b/src/primaite/simulator/system/applications/red_applications/ransomware_script.py index 93b4c50d..3a8ac5ae 100644 --- a/src/primaite/simulator/system/applications/red_applications/ransomware_script.py +++ b/src/primaite/simulator/system/applications/red_applications/ransomware_script.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address from typing import Dict, Optional diff --git a/src/primaite/simulator/system/applications/web_browser.py b/src/primaite/simulator/system/applications/web_browser.py index c57a9bd3..ff185e2a 100644 --- a/src/primaite/simulator/system/applications/web_browser.py +++ b/src/primaite/simulator/system/applications/web_browser.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from enum import Enum from ipaddress import IPv4Address from typing import Dict, List, Optional diff --git a/src/primaite/simulator/system/core/__init__.py b/src/primaite/simulator/system/core/__init__.py index be6c00e7..836b79af 100644 --- a/src/primaite/simulator/system/core/__init__.py +++ b/src/primaite/simulator/system/core/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/system/core/packet_capture.py b/src/primaite/simulator/system/core/packet_capture.py index ea8b00a5..813c288e 100644 --- a/src/primaite/simulator/system/core/packet_capture.py +++ b/src/primaite/simulator/system/core/packet_capture.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import json import logging from pathlib import Path diff --git a/src/primaite/simulator/system/core/session_manager.py b/src/primaite/simulator/system/core/session_manager.py index 75322e86..48f1f383 100644 --- a/src/primaite/simulator/system/core/session_manager.py +++ b/src/primaite/simulator/system/core/session_manager.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from __future__ import annotations from ipaddress import IPv4Address, IPv4Network diff --git a/src/primaite/simulator/system/core/software_manager.py b/src/primaite/simulator/system/core/software_manager.py index 2f19a8b0..5e63f2ec 100644 --- a/src/primaite/simulator/system/core/software_manager.py +++ b/src/primaite/simulator/system/core/software_manager.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from copy import deepcopy from ipaddress import IPv4Address, IPv4Network from typing import Any, Dict, List, Optional, Tuple, TYPE_CHECKING, Union diff --git a/src/primaite/simulator/system/core/sys_log.py b/src/primaite/simulator/system/core/sys_log.py index 9e22696d..741e5d33 100644 --- a/src/primaite/simulator/system/core/sys_log.py +++ b/src/primaite/simulator/system/core/sys_log.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import logging from pathlib import Path diff --git a/src/primaite/simulator/system/processes/__init__.py b/src/primaite/simulator/system/processes/__init__.py index be6c00e7..836b79af 100644 --- a/src/primaite/simulator/system/processes/__init__.py +++ b/src/primaite/simulator/system/processes/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/system/processes/process.py b/src/primaite/simulator/system/processes/process.py index 225505c8..ad2babc1 100644 --- a/src/primaite/simulator/system/processes/process.py +++ b/src/primaite/simulator/system/processes/process.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from abc import abstractmethod from enum import Enum from typing import Dict diff --git a/src/primaite/simulator/system/services/__init__.py b/src/primaite/simulator/system/services/__init__.py index be6c00e7..836b79af 100644 --- a/src/primaite/simulator/system/services/__init__.py +++ b/src/primaite/simulator/system/services/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/system/services/access/__init__.py b/src/primaite/simulator/system/services/access/__init__.py index be6c00e7..836b79af 100644 --- a/src/primaite/simulator/system/services/access/__init__.py +++ b/src/primaite/simulator/system/services/access/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/system/services/access/user_manager.py b/src/primaite/simulator/system/services/access/user_manager.py index be6c00e7..836b79af 100644 --- a/src/primaite/simulator/system/services/access/user_manager.py +++ b/src/primaite/simulator/system/services/access/user_manager.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/system/services/access/user_session_manager.py b/src/primaite/simulator/system/services/access/user_session_manager.py index be6c00e7..836b79af 100644 --- a/src/primaite/simulator/system/services/access/user_session_manager.py +++ b/src/primaite/simulator/system/services/access/user_session_manager.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/system/services/arp/__init__.py b/src/primaite/simulator/system/services/arp/__init__.py index be6c00e7..836b79af 100644 --- a/src/primaite/simulator/system/services/arp/__init__.py +++ b/src/primaite/simulator/system/services/arp/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/system/services/arp/arp.py b/src/primaite/simulator/system/services/arp/arp.py index 816eb99e..31938e83 100644 --- a/src/primaite/simulator/system/services/arp/arp.py +++ b/src/primaite/simulator/system/services/arp/arp.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from __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 be6c00e7..836b79af 100644 --- a/src/primaite/simulator/system/services/database/__init__.py +++ b/src/primaite/simulator/system/services/database/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/system/services/database/database_service.py b/src/primaite/simulator/system/services/database/database_service.py index b7cd8886..3a5f5b31 100644 --- a/src/primaite/simulator/system/services/database/database_service.py +++ b/src/primaite/simulator/system/services/database/database_service.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from 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 be6c00e7..836b79af 100644 --- a/src/primaite/simulator/system/services/dns/__init__.py +++ b/src/primaite/simulator/system/services/dns/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/system/services/dns/dns_client.py b/src/primaite/simulator/system/services/dns/dns_client.py index 78642fa6..02cf54ae 100644 --- a/src/primaite/simulator/system/services/dns/dns_client.py +++ b/src/primaite/simulator/system/services/dns/dns_client.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address from typing import Dict, Optional diff --git a/src/primaite/simulator/system/services/dns/dns_server.py b/src/primaite/simulator/system/services/dns/dns_server.py index 5b380320..b7c9a42c 100644 --- a/src/primaite/simulator/system/services/dns/dns_server.py +++ b/src/primaite/simulator/system/services/dns/dns_server.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from 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 be6c00e7..836b79af 100644 --- a/src/primaite/simulator/system/services/ftp/__init__.py +++ b/src/primaite/simulator/system/services/ftp/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/system/services/ftp/ftp_client.py b/src/primaite/simulator/system/services/ftp/ftp_client.py index 00b70332..9c7b91ce 100644 --- a/src/primaite/simulator/system/services/ftp/ftp_client.py +++ b/src/primaite/simulator/system/services/ftp/ftp_client.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address from typing import Dict, Optional diff --git a/src/primaite/simulator/system/services/ftp/ftp_server.py b/src/primaite/simulator/system/services/ftp/ftp_server.py index 671200f5..9ce7d658 100644 --- a/src/primaite/simulator/system/services/ftp/ftp_server.py +++ b/src/primaite/simulator/system/services/ftp/ftp_server.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import Any, Optional from primaite import getLogger diff --git a/src/primaite/simulator/system/services/ftp/ftp_service.py b/src/primaite/simulator/system/services/ftp/ftp_service.py index 77d82997..52f451e1 100644 --- a/src/primaite/simulator/system/services/ftp/ftp_service.py +++ b/src/primaite/simulator/system/services/ftp/ftp_service.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from abc import ABC from ipaddress import IPv4Address from typing import Dict, Optional diff --git a/src/primaite/simulator/system/services/icmp/__init__.py b/src/primaite/simulator/system/services/icmp/__init__.py index be6c00e7..836b79af 100644 --- a/src/primaite/simulator/system/services/icmp/__init__.py +++ b/src/primaite/simulator/system/services/icmp/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/system/services/icmp/icmp.py b/src/primaite/simulator/system/services/icmp/icmp.py index 84ad995d..933d0591 100644 --- a/src/primaite/simulator/system/services/icmp/icmp.py +++ b/src/primaite/simulator/system/services/icmp/icmp.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import 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 index 19c0ac2d..63fbd4b2 100644 --- a/src/primaite/simulator/system/services/icmp/router_icmp.py +++ b/src/primaite/simulator/system/services/icmp/router_icmp.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © 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. diff --git a/src/primaite/simulator/system/services/ntp/__init__.py b/src/primaite/simulator/system/services/ntp/__init__.py index be6c00e7..836b79af 100644 --- a/src/primaite/simulator/system/services/ntp/__init__.py +++ b/src/primaite/simulator/system/services/ntp/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/system/services/ntp/ntp_client.py b/src/primaite/simulator/system/services/ntp/ntp_client.py index ed89971f..9606c61f 100644 --- a/src/primaite/simulator/system/services/ntp/ntp_client.py +++ b/src/primaite/simulator/system/services/ntp/ntp_client.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from 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 b674a296..6e73ccc6 100644 --- a/src/primaite/simulator/system/services/ntp/ntp_server.py +++ b/src/primaite/simulator/system/services/ntp/ntp_server.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from datetime import datetime from typing import Dict, Optional diff --git a/src/primaite/simulator/system/services/service.py b/src/primaite/simulator/system/services/service.py index 4f0b879c..3dc080b4 100644 --- a/src/primaite/simulator/system/services/service.py +++ b/src/primaite/simulator/system/services/service.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from __future__ import annotations from abc import abstractmethod diff --git a/src/primaite/simulator/system/services/terminal/__init__.py b/src/primaite/simulator/system/services/terminal/__init__.py index be6c00e7..836b79af 100644 --- a/src/primaite/simulator/system/services/terminal/__init__.py +++ b/src/primaite/simulator/system/services/terminal/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/system/services/terminal/terminal.py b/src/primaite/simulator/system/services/terminal/terminal.py index ae3557f7..e26e77f6 100644 --- a/src/primaite/simulator/system/services/terminal/terminal.py +++ b/src/primaite/simulator/system/services/terminal/terminal.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from __future__ import annotations from abc import abstractmethod diff --git a/src/primaite/simulator/system/services/web_server/__init__.py b/src/primaite/simulator/system/services/web_server/__init__.py index be6c00e7..836b79af 100644 --- a/src/primaite/simulator/system/services/web_server/__init__.py +++ b/src/primaite/simulator/system/services/web_server/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK 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 75d9c472..1aab374d 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,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK 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 6fb09a16..34c893eb 100644 --- a/src/primaite/simulator/system/software.py +++ b/src/primaite/simulator/system/software.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import copy from abc import abstractmethod from datetime import datetime diff --git a/src/primaite/utils/__init__.py b/src/primaite/utils/__init__.py index 4d7c430e..1dced372 100644 --- a/src/primaite/utils/__init__.py +++ b/src/primaite/utils/__init__.py @@ -1,2 +1,2 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK """Utilities for PrimAITE.""" diff --git a/src/primaite/utils/cli/__init__.py b/src/primaite/utils/cli/__init__.py index be6c00e7..836b79af 100644 --- a/src/primaite/utils/cli/__init__.py +++ b/src/primaite/utils/cli/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/src/primaite/utils/cli/dev_cli.py b/src/primaite/utils/cli/dev_cli.py index 8946a4ca..581cd0b1 100644 --- a/src/primaite/utils/cli/dev_cli.py +++ b/src/primaite/utils/cli/dev_cli.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import click import typer from rich import print diff --git a/src/primaite/utils/cli/primaite_config_utils.py b/src/primaite/utils/cli/primaite_config_utils.py index 635be5a7..1fefd0a4 100644 --- a/src/primaite/utils/cli/primaite_config_utils.py +++ b/src/primaite/utils/cli/primaite_config_utils.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import Dict, Optional import yaml diff --git a/src/primaite/utils/converters.py b/src/primaite/utils/converters.py index f803851d..95956448 100644 --- a/src/primaite/utils/converters.py +++ b/src/primaite/utils/converters.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from enum import Enum from typing import Any, Dict diff --git a/src/primaite/utils/package_data.py b/src/primaite/utils/package_data.py index af0252f9..ed091dd0 100644 --- a/src/primaite/utils/package_data.py +++ b/src/primaite/utils/package_data.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import os from logging import Logger from pathlib import Path diff --git a/src/primaite/utils/session_metadata_parser.py b/src/primaite/utils/session_metadata_parser.py index f6594666..1a7345ea 100644 --- a/src/primaite/utils/session_metadata_parser.py +++ b/src/primaite/utils/session_metadata_parser.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK # flake8: noqa raise DeprecationWarning( "Benchmarking depends on deprecated functionality and it has not been updated to primaite v3 yet." diff --git a/src/primaite/utils/session_output_reader.py b/src/primaite/utils/session_output_reader.py index b9ad68a1..f25bbe6a 100644 --- a/src/primaite/utils/session_output_reader.py +++ b/src/primaite/utils/session_output_reader.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK # flake8: noqa raise DeprecationWarning( "Benchmarking depends on deprecated functionality and it has not been updated to primaite v3 yet." diff --git a/src/primaite/utils/session_output_writer.py b/src/primaite/utils/session_output_writer.py index 75a97f60..a8cefe35 100644 --- a/src/primaite/utils/session_output_writer.py +++ b/src/primaite/utils/session_output_writer.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK # flake8: noqa raise DeprecationWarning( "Benchmarking depends on deprecated functionality and it has not been updated to primaite v3 yet." diff --git a/src/primaite/utils/validation/__init__.py b/src/primaite/utils/validation/__init__.py index be6c00e7..836b79af 100644 --- a/src/primaite/utils/validation/__init__.py +++ b/src/primaite/utils/validation/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/src/primaite/utils/validation/ip_protocol.py b/src/primaite/utils/validation/ip_protocol.py index 4e358305..654a5156 100644 --- a/src/primaite/utils/validation/ip_protocol.py +++ b/src/primaite/utils/validation/ip_protocol.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK # Define a custom IP protocol validator from typing import Any diff --git a/src/primaite/utils/validation/ipv4_address.py b/src/primaite/utils/validation/ipv4_address.py index eb0e2574..c385ed1e 100644 --- a/src/primaite/utils/validation/ipv4_address.py +++ b/src/primaite/utils/validation/ipv4_address.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address diff --git a/src/primaite/utils/validation/port.py b/src/primaite/utils/validation/port.py index 90c36add..564e843c 100644 --- a/src/primaite/utils/validation/port.py +++ b/src/primaite/utils/validation/port.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK # Define a custom port validator from typing import Any diff --git a/tests/__init__.py b/tests/__init__.py index 846ec808..900649b2 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from pathlib import Path from typing import Final diff --git a/tests/conftest.py b/tests/conftest.py index bd1b79ee..0d73aa07 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import Any, Dict, Tuple import pytest @@ -471,33 +471,6 @@ def game_and_agent(): action_space = ActionManager( actions=actions, # ALL POSSIBLE ACTIONS - nodes=[ - { - "node_name": "client_1", - "applications": [ - {"application_name": "WebBrowser"}, - {"application_name": "DoSBot"}, - {"application_name": "C2Server"}, - ], - "folders": [{"folder_name": "downloads", "files": [{"file_name": "cat.png"}]}], - }, - { - "node_name": "server_1", - "services": [{"service_name": "DNSServer"}], - "applications": [{"application_name": "C2Beacon"}], - }, - {"node_name": "server_2", "services": [{"service_name": "WebServer"}]}, - {"node_name": "router"}, - ], - max_folders_per_node=2, - max_files_per_folder=2, - max_services_per_node=2, - max_applications_per_node=3, - max_nics_per_node=2, - max_acl_rules=10, - protocols=["TCP", "UDP", "ICMP"], - ports=["HTTP", "DNS", "ARP"], - ip_list=["10.0.1.1", "10.0.1.2", "10.0.2.1", "10.0.2.2", "10.0.2.3"], act_map={}, ) observation_space = ObservationManager(NestedObservation(components={})) diff --git a/tests/e2e_integration_tests/__init__.py b/tests/e2e_integration_tests/__init__.py index be6c00e7..836b79af 100644 --- a/tests/e2e_integration_tests/__init__.py +++ b/tests/e2e_integration_tests/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/tests/e2e_integration_tests/action_masking/__init__.py b/tests/e2e_integration_tests/action_masking/__init__.py index be6c00e7..836b79af 100644 --- a/tests/e2e_integration_tests/action_masking/__init__.py +++ b/tests/e2e_integration_tests/action_masking/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/tests/e2e_integration_tests/action_masking/test_agents_use_action_masks.py b/tests/e2e_integration_tests/action_masking/test_agents_use_action_masks.py index addf6dca..a34d430b 100644 --- a/tests/e2e_integration_tests/action_masking/test_agents_use_action_masks.py +++ b/tests/e2e_integration_tests/action_masking/test_agents_use_action_masks.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import Dict import pytest diff --git a/tests/e2e_integration_tests/environments/__init__.py b/tests/e2e_integration_tests/environments/__init__.py index be6c00e7..836b79af 100644 --- a/tests/e2e_integration_tests/environments/__init__.py +++ b/tests/e2e_integration_tests/environments/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/tests/e2e_integration_tests/environments/test_rllib_multi_agent_environment.py b/tests/e2e_integration_tests/environments/test_rllib_multi_agent_environment.py index 26e690d0..06b080d8 100644 --- a/tests/e2e_integration_tests/environments/test_rllib_multi_agent_environment.py +++ b/tests/e2e_integration_tests/environments/test_rllib_multi_agent_environment.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import yaml from ray.rllib.algorithms.ppo import PPOConfig diff --git a/tests/e2e_integration_tests/environments/test_rllib_single_agent_environment.py b/tests/e2e_integration_tests/environments/test_rllib_single_agent_environment.py index 265257e4..da0ca458 100644 --- a/tests/e2e_integration_tests/environments/test_rllib_single_agent_environment.py +++ b/tests/e2e_integration_tests/environments/test_rllib_single_agent_environment.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import tempfile from pathlib import Path diff --git a/tests/e2e_integration_tests/environments/test_sb3_environment.py b/tests/e2e_integration_tests/environments/test_sb3_environment.py index a07d5d2e..9ca3525a 100644 --- a/tests/e2e_integration_tests/environments/test_sb3_environment.py +++ b/tests/e2e_integration_tests/environments/test_sb3_environment.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK """Test that we can create a primaite environment and train sb3 agent with no crash.""" import tempfile from pathlib import Path diff --git a/tests/e2e_integration_tests/test_environment.py b/tests/e2e_integration_tests/test_environment.py index dcd51193..881681aa 100644 --- a/tests/e2e_integration_tests/test_environment.py +++ b/tests/e2e_integration_tests/test_environment.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import pydantic import pytest import yaml diff --git a/tests/e2e_integration_tests/test_uc2_data_manipulation_scenario.py b/tests/e2e_integration_tests/test_uc2_data_manipulation_scenario.py index 7ec38d72..fa4781db 100644 --- a/tests/e2e_integration_tests/test_uc2_data_manipulation_scenario.py +++ b/tests/e2e_integration_tests/test_uc2_data_manipulation_scenario.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import yaml from primaite.game.game import PrimaiteGame diff --git a/tests/integration_tests/__init__.py b/tests/integration_tests/__init__.py index be6c00e7..836b79af 100644 --- a/tests/integration_tests/__init__.py +++ b/tests/integration_tests/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/tests/integration_tests/cli/__init__.py b/tests/integration_tests/cli/__init__.py index cfce7ae6..603d228f 100644 --- a/tests/integration_tests/cli/__init__.py +++ b/tests/integration_tests/cli/__init__.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import List from typer.testing import CliRunner, Result diff --git a/tests/integration_tests/cli/test_dev_cli.py b/tests/integration_tests/cli/test_dev_cli.py index cd390555..16c3de9f 100644 --- a/tests/integration_tests/cli/test_dev_cli.py +++ b/tests/integration_tests/cli/test_dev_cli.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import os import shutil import tempfile diff --git a/tests/integration_tests/component_creation/__init__.py b/tests/integration_tests/component_creation/__init__.py index be6c00e7..836b79af 100644 --- a/tests/integration_tests/component_creation/__init__.py +++ b/tests/integration_tests/component_creation/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/tests/integration_tests/component_creation/test_action_integration.py b/tests/integration_tests/component_creation/test_action_integration.py index 7bdc80fc..8b81b7d3 100644 --- a/tests/integration_tests/component_creation/test_action_integration.py +++ b/tests/integration_tests/component_creation/test_action_integration.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from primaite.simulator.core import RequestType from primaite.simulator.network.hardware.nodes.host.computer import Computer from primaite.simulator.network.hardware.nodes.host.server import Server diff --git a/tests/integration_tests/component_creation/test_permission_system.py b/tests/integration_tests/component_creation/test_permission_system.py index baf75523..c7faa81b 100644 --- a/tests/integration_tests/component_creation/test_permission_system.py +++ b/tests/integration_tests/component_creation/test_permission_system.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from enum import Enum from typing import Dict, List, Literal diff --git a/tests/integration_tests/configuration_file_parsing/__init__.py b/tests/integration_tests/configuration_file_parsing/__init__.py index 7e23a4c2..09861acb 100644 --- a/tests/integration_tests/configuration_file_parsing/__init__.py +++ b/tests/integration_tests/configuration_file_parsing/__init__.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from pathlib import Path from typing import Union diff --git a/tests/integration_tests/configuration_file_parsing/nodes/__init__.py b/tests/integration_tests/configuration_file_parsing/nodes/__init__.py index be6c00e7..836b79af 100644 --- a/tests/integration_tests/configuration_file_parsing/nodes/__init__.py +++ b/tests/integration_tests/configuration_file_parsing/nodes/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/tests/integration_tests/configuration_file_parsing/nodes/network/__init__.py b/tests/integration_tests/configuration_file_parsing/nodes/network/__init__.py index be6c00e7..836b79af 100644 --- a/tests/integration_tests/configuration_file_parsing/nodes/network/__init__.py +++ b/tests/integration_tests/configuration_file_parsing/nodes/network/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/tests/integration_tests/configuration_file_parsing/nodes/network/test_firewall_config.py b/tests/integration_tests/configuration_file_parsing/nodes/network/test_firewall_config.py index 7f251613..234e7342 100644 --- a/tests/integration_tests/configuration_file_parsing/nodes/network/test_firewall_config.py +++ b/tests/integration_tests/configuration_file_parsing/nodes/network/test_firewall_config.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address import pytest diff --git a/tests/integration_tests/configuration_file_parsing/nodes/network/test_router_config.py b/tests/integration_tests/configuration_file_parsing/nodes/network/test_router_config.py index d10c7dbb..16f4dee5 100644 --- a/tests/integration_tests/configuration_file_parsing/nodes/network/test_router_config.py +++ b/tests/integration_tests/configuration_file_parsing/nodes/network/test_router_config.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import pytest from primaite.simulator.network.container import Network diff --git a/tests/integration_tests/configuration_file_parsing/nodes/test_node_config.py b/tests/integration_tests/configuration_file_parsing/nodes/test_node_config.py index 8526ab78..764a7aac 100644 --- a/tests/integration_tests/configuration_file_parsing/nodes/test_node_config.py +++ b/tests/integration_tests/configuration_file_parsing/nodes/test_node_config.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from primaite.config.load import data_manipulation_config_path from primaite.simulator.network.container import Network from primaite.simulator.network.hardware.node_operating_state import NodeOperatingState diff --git a/tests/integration_tests/configuration_file_parsing/software_installation_and_configuration.py b/tests/integration_tests/configuration_file_parsing/software_installation_and_configuration.py index a642564c..0ff6754d 100644 --- a/tests/integration_tests/configuration_file_parsing/software_installation_and_configuration.py +++ b/tests/integration_tests/configuration_file_parsing/software_installation_and_configuration.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address from pathlib import Path from typing import Union diff --git a/tests/integration_tests/configuration_file_parsing/test_episode_scheduler.py b/tests/integration_tests/configuration_file_parsing/test_episode_scheduler.py index 13be830b..c588829b 100644 --- a/tests/integration_tests/configuration_file_parsing/test_episode_scheduler.py +++ b/tests/integration_tests/configuration_file_parsing/test_episode_scheduler.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import pytest import yaml diff --git a/tests/integration_tests/configuration_file_parsing/test_game_options_config.py b/tests/integration_tests/configuration_file_parsing/test_game_options_config.py index 32d88c92..4153adc0 100644 --- a/tests/integration_tests/configuration_file_parsing/test_game_options_config.py +++ b/tests/integration_tests/configuration_file_parsing/test_game_options_config.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from pathlib import Path from typing import Union diff --git a/tests/integration_tests/configuration_file_parsing/test_io_settings.py b/tests/integration_tests/configuration_file_parsing/test_io_settings.py index 82977b82..79812d80 100644 --- a/tests/integration_tests/configuration_file_parsing/test_io_settings.py +++ b/tests/integration_tests/configuration_file_parsing/test_io_settings.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from pathlib import Path from typing import Union diff --git a/tests/integration_tests/configuration_file_parsing/test_no_nodes_links_agents_config.py b/tests/integration_tests/configuration_file_parsing/test_no_nodes_links_agents_config.py index 26fc562d..016d264f 100644 --- a/tests/integration_tests/configuration_file_parsing/test_no_nodes_links_agents_config.py +++ b/tests/integration_tests/configuration_file_parsing/test_no_nodes_links_agents_config.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import yaml from primaite.game.game import PrimaiteGame diff --git a/tests/integration_tests/configuration_file_parsing/test_software_fix_duration.py b/tests/integration_tests/configuration_file_parsing/test_software_fix_duration.py index 168ebee0..b1c644cc 100644 --- a/tests/integration_tests/configuration_file_parsing/test_software_fix_duration.py +++ b/tests/integration_tests/configuration_file_parsing/test_software_fix_duration.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import copy from pathlib import Path from typing import Union diff --git a/tests/integration_tests/extensions/applications/extended_application.py b/tests/integration_tests/extensions/applications/extended_application.py index 70dc7cba..9863dbba 100644 --- a/tests/integration_tests/extensions/applications/extended_application.py +++ b/tests/integration_tests/extensions/applications/extended_application.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from enum import Enum from ipaddress import IPv4Address from typing import Dict, List, Optional diff --git a/tests/integration_tests/extensions/nodes/giga_switch.py b/tests/integration_tests/extensions/nodes/giga_switch.py index e4100741..37a05b6e 100644 --- a/tests/integration_tests/extensions/nodes/giga_switch.py +++ b/tests/integration_tests/extensions/nodes/giga_switch.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import Dict from prettytable import MARKDOWN, PrettyTable diff --git a/tests/integration_tests/extensions/nodes/super_computer.py b/tests/integration_tests/extensions/nodes/super_computer.py index 80f7e3c3..4af1b748 100644 --- a/tests/integration_tests/extensions/nodes/super_computer.py +++ b/tests/integration_tests/extensions/nodes/super_computer.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import ClassVar, Dict from primaite.simulator.network.hardware.nodes.host.host_node import HostNode, NIC diff --git a/tests/integration_tests/extensions/services/extended_service.py b/tests/integration_tests/extensions/services/extended_service.py index ddaf4a1e..0924a91b 100644 --- a/tests/integration_tests/extensions/services/extended_service.py +++ b/tests/integration_tests/extensions/services/extended_service.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address from typing import Any, Dict, List, Literal, Optional, Union from uuid import uuid4 diff --git a/tests/integration_tests/extensions/test_extendable_config.py b/tests/integration_tests/extensions/test_extendable_config.py index 5addcbd7..5515d900 100644 --- a/tests/integration_tests/extensions/test_extendable_config.py +++ b/tests/integration_tests/extensions/test_extendable_config.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import os from primaite.config.load import get_extended_config_path diff --git a/tests/integration_tests/game_layer/actions/__init__.py b/tests/integration_tests/game_layer/actions/__init__.py index be6c00e7..836b79af 100644 --- a/tests/integration_tests/game_layer/actions/__init__.py +++ b/tests/integration_tests/game_layer/actions/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/tests/integration_tests/game_layer/actions/test_application_request_permission.py b/tests/integration_tests/game_layer/actions/test_application_request_permission.py index 36a7ae57..e90fa591 100644 --- a/tests/integration_tests/game_layer/actions/test_application_request_permission.py +++ b/tests/integration_tests/game_layer/actions/test_application_request_permission.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import Tuple import pytest diff --git a/tests/integration_tests/game_layer/actions/test_c2_suite_actions.py b/tests/integration_tests/game_layer/actions/test_c2_suite_actions.py index d73c9834..36fee9a0 100644 --- a/tests/integration_tests/game_layer/actions/test_c2_suite_actions.py +++ b/tests/integration_tests/game_layer/actions/test_c2_suite_actions.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address from typing import Tuple diff --git a/tests/integration_tests/game_layer/actions/test_configure_actions.py b/tests/integration_tests/game_layer/actions/test_configure_actions.py index 7bf45fb4..8c97573a 100644 --- a/tests/integration_tests/game_layer/actions/test_configure_actions.py +++ b/tests/integration_tests/game_layer/actions/test_configure_actions.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address import pytest diff --git a/tests/integration_tests/game_layer/actions/test_file_request_permission.py b/tests/integration_tests/game_layer/actions/test_file_request_permission.py index 1c143aed..91aa9fcd 100644 --- a/tests/integration_tests/game_layer/actions/test_file_request_permission.py +++ b/tests/integration_tests/game_layer/actions/test_file_request_permission.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import uuid from typing import Tuple diff --git a/tests/integration_tests/game_layer/actions/test_folder_request_permission.py b/tests/integration_tests/game_layer/actions/test_folder_request_permission.py index e5e0806a..56bbbd4e 100644 --- a/tests/integration_tests/game_layer/actions/test_folder_request_permission.py +++ b/tests/integration_tests/game_layer/actions/test_folder_request_permission.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import uuid from typing import Tuple diff --git a/tests/integration_tests/game_layer/actions/test_nic_request_permission.py b/tests/integration_tests/game_layer/actions/test_nic_request_permission.py index d796b75e..8846809d 100644 --- a/tests/integration_tests/game_layer/actions/test_nic_request_permission.py +++ b/tests/integration_tests/game_layer/actions/test_nic_request_permission.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import Tuple import pytest diff --git a/tests/integration_tests/game_layer/actions/test_node_request_permission.py b/tests/integration_tests/game_layer/actions/test_node_request_permission.py index c34103bc..8fbbbd70 100644 --- a/tests/integration_tests/game_layer/actions/test_node_request_permission.py +++ b/tests/integration_tests/game_layer/actions/test_node_request_permission.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import Tuple import pytest diff --git a/tests/integration_tests/game_layer/actions/test_service_request_permission.py b/tests/integration_tests/game_layer/actions/test_service_request_permission.py index 3054c73b..ebc9fd3b 100644 --- a/tests/integration_tests/game_layer/actions/test_service_request_permission.py +++ b/tests/integration_tests/game_layer/actions/test_service_request_permission.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import Tuple import pytest diff --git a/tests/integration_tests/game_layer/actions/test_terminal_actions.py b/tests/integration_tests/game_layer/actions/test_terminal_actions.py index a70cea72..96110656 100644 --- a/tests/integration_tests/game_layer/actions/test_terminal_actions.py +++ b/tests/integration_tests/game_layer/actions/test_terminal_actions.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import Tuple import pytest diff --git a/tests/integration_tests/game_layer/observations/__init__.py b/tests/integration_tests/game_layer/observations/__init__.py index be6c00e7..836b79af 100644 --- a/tests/integration_tests/game_layer/observations/__init__.py +++ b/tests/integration_tests/game_layer/observations/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/tests/integration_tests/game_layer/observations/test_acl_observations.py b/tests/integration_tests/game_layer/observations/test_acl_observations.py index e7212f3c..02cf005a 100644 --- a/tests/integration_tests/game_layer/observations/test_acl_observations.py +++ b/tests/integration_tests/game_layer/observations/test_acl_observations.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import pytest from primaite.game.agent.observations.acl_observation import ACLObservation diff --git a/tests/integration_tests/game_layer/observations/test_file_system_observations.py b/tests/integration_tests/game_layer/observations/test_file_system_observations.py index e2ab2990..0268cb95 100644 --- a/tests/integration_tests/game_layer/observations/test_file_system_observations.py +++ b/tests/integration_tests/game_layer/observations/test_file_system_observations.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import pytest from gymnasium import spaces diff --git a/tests/integration_tests/game_layer/observations/test_firewall_observation.py b/tests/integration_tests/game_layer/observations/test_firewall_observation.py index 05cf910c..97608132 100644 --- a/tests/integration_tests/game_layer/observations/test_firewall_observation.py +++ b/tests/integration_tests/game_layer/observations/test_firewall_observation.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from primaite.game.agent.observations.firewall_observation import FirewallObservation from primaite.simulator.network.container import Network from primaite.simulator.network.hardware.node_operating_state import NodeOperatingState diff --git a/tests/integration_tests/game_layer/observations/test_link_observations.py b/tests/integration_tests/game_layer/observations/test_link_observations.py index 7d1c1939..630e29ea 100644 --- a/tests/integration_tests/game_layer/observations/test_link_observations.py +++ b/tests/integration_tests/game_layer/observations/test_link_observations.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import pytest from gymnasium import spaces diff --git a/tests/integration_tests/game_layer/observations/test_nic_observations.py b/tests/integration_tests/game_layer/observations/test_nic_observations.py index 8254dad2..0ad03198 100644 --- a/tests/integration_tests/game_layer/observations/test_nic_observations.py +++ b/tests/integration_tests/game_layer/observations/test_nic_observations.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from pathlib import Path from typing import Union diff --git a/tests/integration_tests/game_layer/observations/test_node_observations.py b/tests/integration_tests/game_layer/observations/test_node_observations.py index 69d9f106..63ca8f6b 100644 --- a/tests/integration_tests/game_layer/observations/test_node_observations.py +++ b/tests/integration_tests/game_layer/observations/test_node_observations.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import copy from uuid import uuid4 diff --git a/tests/integration_tests/game_layer/observations/test_router_observation.py b/tests/integration_tests/game_layer/observations/test_router_observation.py index 4ced02f5..f4bfb193 100644 --- a/tests/integration_tests/game_layer/observations/test_router_observation.py +++ b/tests/integration_tests/game_layer/observations/test_router_observation.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from pprint import pprint from primaite.game.agent.observations.acl_observation import ACLObservation diff --git a/tests/integration_tests/game_layer/observations/test_software_observations.py b/tests/integration_tests/game_layer/observations/test_software_observations.py index 998aa755..291ee395 100644 --- a/tests/integration_tests/game_layer/observations/test_software_observations.py +++ b/tests/integration_tests/game_layer/observations/test_software_observations.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import pytest from gymnasium import spaces diff --git a/tests/integration_tests/game_layer/observations/test_user_observations.py b/tests/integration_tests/game_layer/observations/test_user_observations.py index e7287eee..92c533c9 100644 --- a/tests/integration_tests/game_layer/observations/test_user_observations.py +++ b/tests/integration_tests/game_layer/observations/test_user_observations.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import pytest from primaite.session.environment import PrimaiteGymEnv diff --git a/tests/integration_tests/game_layer/test_RNG_seed.py b/tests/integration_tests/game_layer/test_RNG_seed.py index e772af32..464f95db 100644 --- a/tests/integration_tests/game_layer/test_RNG_seed.py +++ b/tests/integration_tests/game_layer/test_RNG_seed.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from pprint import pprint import pytest diff --git a/tests/integration_tests/game_layer/test_action_mask.py b/tests/integration_tests/game_layer/test_action_mask.py index 7a1475c2..22c00aa4 100644 --- a/tests/integration_tests/game_layer/test_action_mask.py +++ b/tests/integration_tests/game_layer/test_action_mask.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from primaite.session.environment import PrimaiteGymEnv from primaite.simulator.network.hardware.node_operating_state import NodeOperatingState from primaite.simulator.network.hardware.nodes.host.host_node import HostNode diff --git a/tests/integration_tests/game_layer/test_actions.py b/tests/integration_tests/game_layer/test_actions.py index 859c056c..ff86dbf0 100644 --- a/tests/integration_tests/game_layer/test_actions.py +++ b/tests/integration_tests/game_layer/test_actions.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK # Plan for creating integration tests for the actions: # I need to test that the requests coming out of the actions have the intended effect on the simulation. # I can do this by creating a simulation, and then running the action on the simulation, and then checking diff --git a/tests/integration_tests/game_layer/test_observations.py b/tests/integration_tests/game_layer/test_observations.py index d5679007..23364f13 100644 --- a/tests/integration_tests/game_layer/test_observations.py +++ b/tests/integration_tests/game_layer/test_observations.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from gymnasium import spaces from primaite.game.agent.observations.file_system_observations import FileObservation diff --git a/tests/integration_tests/game_layer/test_rewards.py b/tests/integration_tests/game_layer/test_rewards.py index 882c0923..a2453782 100644 --- a/tests/integration_tests/game_layer/test_rewards.py +++ b/tests/integration_tests/game_layer/test_rewards.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import pytest import yaml diff --git a/tests/integration_tests/network/__init__.py b/tests/integration_tests/network/__init__.py index be6c00e7..836b79af 100644 --- a/tests/integration_tests/network/__init__.py +++ b/tests/integration_tests/network/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/tests/integration_tests/network/test_airspace_config.py b/tests/integration_tests/network/test_airspace_config.py index e000f6ae..e8abc0f2 100644 --- a/tests/integration_tests/network/test_airspace_config.py +++ b/tests/integration_tests/network/test_airspace_config.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import yaml from primaite.game.game import PrimaiteGame diff --git a/tests/integration_tests/network/test_bandwidth_load_checks_before_transmission.py b/tests/integration_tests/network/test_bandwidth_load_checks_before_transmission.py index b7317c3d..36c77fe1 100644 --- a/tests/integration_tests/network/test_bandwidth_load_checks_before_transmission.py +++ b/tests/integration_tests/network/test_bandwidth_load_checks_before_transmission.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from primaite.simulator.file_system.file_type import FileType from primaite.simulator.network.hardware.nodes.network.router import ACLAction from primaite.simulator.system.services.ftp.ftp_client import FTPClient diff --git a/tests/integration_tests/network/test_broadcast.py b/tests/integration_tests/network/test_broadcast.py index f07f02e7..33fe70c3 100644 --- a/tests/integration_tests/network/test_broadcast.py +++ b/tests/integration_tests/network/test_broadcast.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address, IPv4Network from typing import Any, Dict, List, Tuple diff --git a/tests/integration_tests/network/test_capture_nmne.py b/tests/integration_tests/network/test_capture_nmne.py index debf5b1c..b32d9657 100644 --- a/tests/integration_tests/network/test_capture_nmne.py +++ b/tests/integration_tests/network/test_capture_nmne.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from primaite.game.agent.observations.nic_observations import NICObservation from primaite.simulator.network.container import Network from primaite.simulator.network.hardware.nodes.host.host_node import NIC diff --git a/tests/integration_tests/network/test_firewall.py b/tests/integration_tests/network/test_firewall.py index 79452318..24fbfd05 100644 --- a/tests/integration_tests/network/test_firewall.py +++ b/tests/integration_tests/network/test_firewall.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address import pytest diff --git a/tests/integration_tests/network/test_frame_transmission.py b/tests/integration_tests/network/test_frame_transmission.py index fc2d146e..327c87e5 100644 --- a/tests/integration_tests/network/test_frame_transmission.py +++ b/tests/integration_tests/network/test_frame_transmission.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from primaite.simulator.network.container import Network from primaite.simulator.network.hardware.nodes.host.computer import Computer from primaite.simulator.network.hardware.nodes.host.host_node import NIC diff --git a/tests/integration_tests/network/test_multi_lan_internet_example_network.py b/tests/integration_tests/network/test_multi_lan_internet_example_network.py index bcc9ad94..ea7e1c45 100644 --- a/tests/integration_tests/network/test_multi_lan_internet_example_network.py +++ b/tests/integration_tests/network/test_multi_lan_internet_example_network.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from primaite.simulator.network.hardware.nodes.host.computer import Computer from primaite.simulator.network.hardware.nodes.host.server import Server from primaite.simulator.network.networks import multi_lan_internet_network_example diff --git a/tests/integration_tests/network/test_network_creation.py b/tests/integration_tests/network/test_network_creation.py index 794ddde5..1ee3ccc2 100644 --- a/tests/integration_tests/network/test_network_creation.py +++ b/tests/integration_tests/network/test_network_creation.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from primaite.simulator.network.container import Network from primaite.simulator.network.hardware.nodes.host.computer import Computer from primaite.simulator.network.hardware.nodes.host.host_node import NIC diff --git a/tests/integration_tests/network/test_nic_link_connection.py b/tests/integration_tests/network/test_nic_link_connection.py index ab9160c8..8c45f511 100644 --- a/tests/integration_tests/network/test_nic_link_connection.py +++ b/tests/integration_tests/network/test_nic_link_connection.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import pytest from primaite.simulator.network.hardware.base import Link diff --git a/tests/integration_tests/network/test_routing.py b/tests/integration_tests/network/test_routing.py index 04cdbe78..948b409f 100644 --- a/tests/integration_tests/network/test_routing.py +++ b/tests/integration_tests/network/test_routing.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import Tuple import pytest diff --git a/tests/integration_tests/network/test_switched_network.py b/tests/integration_tests/network/test_switched_network.py index ae0aa8a7..67392da3 100644 --- a/tests/integration_tests/network/test_switched_network.py +++ b/tests/integration_tests/network/test_switched_network.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK def test_switched_network(client_switch_server): """Tests a node can ping another node via the switch.""" computer, switch, server = client_switch_server diff --git a/tests/integration_tests/network/test_users_creation_from_config.py b/tests/integration_tests/network/test_users_creation_from_config.py index 8cd3b037..1963b1dd 100644 --- a/tests/integration_tests/network/test_users_creation_from_config.py +++ b/tests/integration_tests/network/test_users_creation_from_config.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import yaml from primaite.game.game import PrimaiteGame diff --git a/tests/integration_tests/network/test_wireless_router.py b/tests/integration_tests/network/test_wireless_router.py index fb0035e9..26e50f4a 100644 --- a/tests/integration_tests/network/test_wireless_router.py +++ b/tests/integration_tests/network/test_wireless_router.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import pytest import yaml diff --git a/tests/integration_tests/system/__init__.py b/tests/integration_tests/system/__init__.py index be6c00e7..836b79af 100644 --- a/tests/integration_tests/system/__init__.py +++ b/tests/integration_tests/system/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/tests/integration_tests/system/red_applications/test_c2_suite_integration.py b/tests/integration_tests/system/red_applications/test_c2_suite_integration.py index 2cbd4d11..d88f8249 100644 --- a/tests/integration_tests/system/red_applications/test_c2_suite_integration.py +++ b/tests/integration_tests/system/red_applications/test_c2_suite_integration.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address from typing import Tuple diff --git a/tests/integration_tests/system/red_applications/test_data_manipulation_bot_and_server.py b/tests/integration_tests/system/red_applications/test_data_manipulation_bot_and_server.py index 50b0ceac..3ef6469e 100644 --- a/tests/integration_tests/system/red_applications/test_data_manipulation_bot_and_server.py +++ b/tests/integration_tests/system/red_applications/test_data_manipulation_bot_and_server.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address from typing import Tuple diff --git a/tests/integration_tests/system/red_applications/test_dos_bot_and_server.py b/tests/integration_tests/system/red_applications/test_dos_bot_and_server.py index 1a09e875..cb0195f0 100644 --- a/tests/integration_tests/system/red_applications/test_dos_bot_and_server.py +++ b/tests/integration_tests/system/red_applications/test_dos_bot_and_server.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address from typing import Tuple diff --git a/tests/integration_tests/system/red_applications/test_ransomware_script.py b/tests/integration_tests/system/red_applications/test_ransomware_script.py index a5adbb04..14b83e6a 100644 --- a/tests/integration_tests/system/red_applications/test_ransomware_script.py +++ b/tests/integration_tests/system/red_applications/test_ransomware_script.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address from typing import Tuple diff --git a/tests/integration_tests/system/test_application_on_node.py b/tests/integration_tests/system/test_application_on_node.py index ffb5cc7f..fc7aa69c 100644 --- a/tests/integration_tests/system/test_application_on_node.py +++ b/tests/integration_tests/system/test_application_on_node.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import Tuple import pytest diff --git a/tests/integration_tests/system/test_arp.py b/tests/integration_tests/system/test_arp.py index be8656aa..055d58c6 100644 --- a/tests/integration_tests/system/test_arp.py +++ b/tests/integration_tests/system/test_arp.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from primaite.simulator.network.hardware.nodes.network.router import RouterARP from primaite.simulator.system.services.arp.arp import ARP from tests.integration_tests.network.test_routing import multi_hop_network diff --git a/tests/integration_tests/system/test_database_on_node.py b/tests/integration_tests/system/test_database_on_node.py index 965b4ae8..674603fa 100644 --- a/tests/integration_tests/system/test_database_on_node.py +++ b/tests/integration_tests/system/test_database_on_node.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address from typing import Tuple diff --git a/tests/integration_tests/system/test_dns_client_server.py b/tests/integration_tests/system/test_dns_client_server.py index 480a90bc..38caf1a2 100644 --- a/tests/integration_tests/system/test_dns_client_server.py +++ b/tests/integration_tests/system/test_dns_client_server.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address from typing import Tuple diff --git a/tests/integration_tests/system/test_ftp_client_server.py b/tests/integration_tests/system/test_ftp_client_server.py index 22c5d484..fa4df0a9 100644 --- a/tests/integration_tests/system/test_ftp_client_server.py +++ b/tests/integration_tests/system/test_ftp_client_server.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import Tuple import pytest diff --git a/tests/integration_tests/system/test_nmap.py b/tests/integration_tests/system/test_nmap.py index c52b5caa..d1925a94 100644 --- a/tests/integration_tests/system/test_nmap.py +++ b/tests/integration_tests/system/test_nmap.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from enum import Enum from ipaddress import IPv4Address, IPv4Network diff --git a/tests/integration_tests/system/test_ntp_client_server.py b/tests/integration_tests/system/test_ntp_client_server.py index 957c1aeb..42340eb3 100644 --- a/tests/integration_tests/system/test_ntp_client_server.py +++ b/tests/integration_tests/system/test_ntp_client_server.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address from time import sleep from typing import Tuple diff --git a/tests/integration_tests/system/test_service_listening_on_ports.py b/tests/integration_tests/system/test_service_listening_on_ports.py index 7a085ee1..2d3679ed 100644 --- a/tests/integration_tests/system/test_service_listening_on_ports.py +++ b/tests/integration_tests/system/test_service_listening_on_ports.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import Any, Dict, List, Set import yaml diff --git a/tests/integration_tests/system/test_service_on_node.py b/tests/integration_tests/system/test_service_on_node.py index cf9728ce..4e73a050 100644 --- a/tests/integration_tests/system/test_service_on_node.py +++ b/tests/integration_tests/system/test_service_on_node.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import Tuple import pytest diff --git a/tests/integration_tests/system/test_user_session_manager_logins.py b/tests/integration_tests/system/test_user_session_manager_logins.py index 4318530c..0c591a4b 100644 --- a/tests/integration_tests/system/test_user_session_manager_logins.py +++ b/tests/integration_tests/system/test_user_session_manager_logins.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import Tuple from uuid import uuid4 diff --git a/tests/integration_tests/system/test_web_client_server.py b/tests/integration_tests/system/test_web_client_server.py index 05cbae4f..c1028e8e 100644 --- a/tests/integration_tests/system/test_web_client_server.py +++ b/tests/integration_tests/system/test_web_client_server.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import Tuple import pytest diff --git a/tests/integration_tests/system/test_web_client_server_and_database.py b/tests/integration_tests/system/test_web_client_server_and_database.py index f2ac1183..8fb6dc18 100644 --- a/tests/integration_tests/system/test_web_client_server_and_database.py +++ b/tests/integration_tests/system/test_web_client_server_and_database.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address from typing import Tuple diff --git a/tests/integration_tests/test_simulation/__init__.py b/tests/integration_tests/test_simulation/__init__.py index be6c00e7..836b79af 100644 --- a/tests/integration_tests/test_simulation/__init__.py +++ b/tests/integration_tests/test_simulation/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/tests/integration_tests/test_simulation/test_request_response.py b/tests/integration_tests/test_simulation/test_request_response.py index a767f365..21152199 100644 --- a/tests/integration_tests/test_simulation/test_request_response.py +++ b/tests/integration_tests/test_simulation/test_request_response.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK # some test cases: # 0. test that sending a request to a valid target results in a success # 1. test that sending a request to a component that doesn't exist results in a failure diff --git a/tests/mock_and_patch/__init__.py b/tests/mock_and_patch/__init__.py index be6c00e7..836b79af 100644 --- a/tests/mock_and_patch/__init__.py +++ b/tests/mock_and_patch/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/tests/mock_and_patch/get_session_path_mock.py b/tests/mock_and_patch/get_session_path_mock.py index f315fca4..073028a7 100644 --- a/tests/mock_and_patch/get_session_path_mock.py +++ b/tests/mock_and_patch/get_session_path_mock.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import tempfile from datetime import datetime from pathlib import Path diff --git a/tests/unit_tests/__init__.py b/tests/unit_tests/__init__.py index be6c00e7..836b79af 100644 --- a/tests/unit_tests/__init__.py +++ b/tests/unit_tests/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/tests/unit_tests/_primaite/__init__.py b/tests/unit_tests/_primaite/__init__.py index be6c00e7..836b79af 100644 --- a/tests/unit_tests/_primaite/__init__.py +++ b/tests/unit_tests/_primaite/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/tests/unit_tests/_primaite/_game/__init__.py b/tests/unit_tests/_primaite/_game/__init__.py index be6c00e7..836b79af 100644 --- a/tests/unit_tests/_primaite/_game/__init__.py +++ b/tests/unit_tests/_primaite/_game/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/tests/unit_tests/_primaite/_game/_agent/__init__.py b/tests/unit_tests/_primaite/_game/_agent/__init__.py index be6c00e7..836b79af 100644 --- a/tests/unit_tests/_primaite/_game/_agent/__init__.py +++ b/tests/unit_tests/_primaite/_game/_agent/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/tests/unit_tests/_primaite/_game/_agent/test_actions.py b/tests/unit_tests/_primaite/_game/_agent/test_actions.py index 46963015..9021b8af 100644 --- a/tests/unit_tests/_primaite/_game/_agent/test_actions.py +++ b/tests/unit_tests/_primaite/_game/_agent/test_actions.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from unittest.mock import Mock import pytest diff --git a/tests/unit_tests/_primaite/_game/_agent/test_agent_log.py b/tests/unit_tests/_primaite/_game/_agent/test_agent_log.py index d61e1a23..a7713437 100644 --- a/tests/unit_tests/_primaite/_game/_agent/test_agent_log.py +++ b/tests/unit_tests/_primaite/_game/_agent/test_agent_log.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from uuid import uuid4 import pytest diff --git a/tests/unit_tests/_primaite/_game/_agent/test_observations.py b/tests/unit_tests/_primaite/_game/_agent/test_observations.py index 7f590685..bb3ad33c 100644 --- a/tests/unit_tests/_primaite/_game/_agent/test_observations.py +++ b/tests/unit_tests/_primaite/_game/_agent/test_observations.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import List import pytest diff --git a/tests/unit_tests/_primaite/_game/_agent/test_probabilistic_agent.py b/tests/unit_tests/_primaite/_game/_agent/test_probabilistic_agent.py index 2fd2da0c..8c06aeed 100644 --- a/tests/unit_tests/_primaite/_game/_agent/test_probabilistic_agent.py +++ b/tests/unit_tests/_primaite/_game/_agent/test_probabilistic_agent.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from primaite.game.agent.actions import ActionManager from primaite.game.agent.observations.observation_manager import NestedObservation, ObservationManager from primaite.game.agent.rewards import RewardFunction diff --git a/tests/unit_tests/_primaite/_game/_agent/test_sticky_rewards.py b/tests/unit_tests/_primaite/_game/_agent/test_sticky_rewards.py index 78113f5f..67c4290d 100644 --- a/tests/unit_tests/_primaite/_game/_agent/test_sticky_rewards.py +++ b/tests/unit_tests/_primaite/_game/_agent/test_sticky_rewards.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from primaite.game.agent.interface import AgentHistoryItem from primaite.game.agent.rewards import ( diff --git a/tests/unit_tests/_primaite/_interface/__init__.py b/tests/unit_tests/_primaite/_interface/__init__.py index be6c00e7..836b79af 100644 --- a/tests/unit_tests/_primaite/_interface/__init__.py +++ b/tests/unit_tests/_primaite/_interface/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/tests/unit_tests/_primaite/_interface/test_request.py b/tests/unit_tests/_primaite/_interface/test_request.py index 6067f9e4..d9fae083 100644 --- a/tests/unit_tests/_primaite/_interface/test_request.py +++ b/tests/unit_tests/_primaite/_interface/test_request.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import pytest from pydantic import ValidationError diff --git a/tests/unit_tests/_primaite/_session/__init__.py b/tests/unit_tests/_primaite/_session/__init__.py index be6c00e7..836b79af 100644 --- a/tests/unit_tests/_primaite/_session/__init__.py +++ b/tests/unit_tests/_primaite/_session/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/tests/unit_tests/_primaite/_session/test_episode_schedule.py b/tests/unit_tests/_primaite/_session/test_episode_schedule.py index 21448339..ff26bb02 100644 --- a/tests/unit_tests/_primaite/_session/test_episode_schedule.py +++ b/tests/unit_tests/_primaite/_session/test_episode_schedule.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import pytest import yaml diff --git a/tests/unit_tests/_primaite/_simulator/__init__.py b/tests/unit_tests/_primaite/_simulator/__init__.py index be6c00e7..836b79af 100644 --- a/tests/unit_tests/_primaite/_simulator/__init__.py +++ b/tests/unit_tests/_primaite/_simulator/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/tests/unit_tests/_primaite/_simulator/_domain/__init__.py b/tests/unit_tests/_primaite/_simulator/_domain/__init__.py index be6c00e7..836b79af 100644 --- a/tests/unit_tests/_primaite/_simulator/_domain/__init__.py +++ b/tests/unit_tests/_primaite/_simulator/_domain/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/tests/unit_tests/_primaite/_simulator/_domain/test_account.py b/tests/unit_tests/_primaite/_simulator/_domain/test_account.py index 8db68565..f5294844 100644 --- a/tests/unit_tests/_primaite/_simulator/_domain/test_account.py +++ b/tests/unit_tests/_primaite/_simulator/_domain/test_account.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK """Test the account module of the simulator.""" import pytest diff --git a/tests/unit_tests/_primaite/_simulator/_domain/test_controller.py b/tests/unit_tests/_primaite/_simulator/_domain/test_controller.py index be6c00e7..836b79af 100644 --- a/tests/unit_tests/_primaite/_simulator/_domain/test_controller.py +++ b/tests/unit_tests/_primaite/_simulator/_domain/test_controller.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/tests/unit_tests/_primaite/_simulator/_file_system/__init__.py b/tests/unit_tests/_primaite/_simulator/_file_system/__init__.py index be6c00e7..836b79af 100644 --- a/tests/unit_tests/_primaite/_simulator/_file_system/__init__.py +++ b/tests/unit_tests/_primaite/_simulator/_file_system/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/tests/unit_tests/_primaite/_simulator/_file_system/test_file.py b/tests/unit_tests/_primaite/_simulator/_file_system/test_file.py index 0b9bdc8e..6cbf93c8 100644 --- a/tests/unit_tests/_primaite/_simulator/_file_system/test_file.py +++ b/tests/unit_tests/_primaite/_simulator/_file_system/test_file.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import warnings import pytest diff --git a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_actions.py b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_actions.py index 594c7afe..4ec1ec57 100644 --- a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_actions.py +++ b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_actions.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import Tuple import pytest diff --git a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system.py b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system.py index 4eb0dd10..5554b9ef 100644 --- a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system.py +++ b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import pytest from primaite.simulator.file_system.file import File diff --git a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_actions.py b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_actions.py index 7d022ea4..44a4e22a 100644 --- a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_actions.py +++ b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_actions.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import Tuple import pytest diff --git a/tests/unit_tests/_primaite/_simulator/_file_system/test_folder.py b/tests/unit_tests/_primaite/_simulator/_file_system/test_folder.py index 724d7903..473e0db2 100644 --- a/tests/unit_tests/_primaite/_simulator/_file_system/test_folder.py +++ b/tests/unit_tests/_primaite/_simulator/_file_system/test_folder.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import pytest from primaite.simulator.file_system.file import File diff --git a/tests/unit_tests/_primaite/_simulator/_file_system/test_folder_actions.py b/tests/unit_tests/_primaite/_simulator/_file_system/test_folder_actions.py index 4a561b97..609e29c4 100644 --- a/tests/unit_tests/_primaite/_simulator/_file_system/test_folder_actions.py +++ b/tests/unit_tests/_primaite/_simulator/_file_system/test_folder_actions.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import warnings from typing import Tuple diff --git a/tests/unit_tests/_primaite/_simulator/_network/__init__.py b/tests/unit_tests/_primaite/_simulator/_network/__init__.py index be6c00e7..836b79af 100644 --- a/tests/unit_tests/_primaite/_simulator/_network/__init__.py +++ b/tests/unit_tests/_primaite/_simulator/_network/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/tests/unit_tests/_primaite/_simulator/_network/_hardware/__init__.py b/tests/unit_tests/_primaite/_simulator/_network/_hardware/__init__.py index be6c00e7..836b79af 100644 --- a/tests/unit_tests/_primaite/_simulator/_network/_hardware/__init__.py +++ b/tests/unit_tests/_primaite/_simulator/_network/_hardware/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/tests/unit_tests/_primaite/_simulator/_network/_hardware/nodes/__init__.py b/tests/unit_tests/_primaite/_simulator/_network/_hardware/nodes/__init__.py index be6c00e7..836b79af 100644 --- a/tests/unit_tests/_primaite/_simulator/_network/_hardware/nodes/__init__.py +++ b/tests/unit_tests/_primaite/_simulator/_network/_hardware/nodes/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/tests/unit_tests/_primaite/_simulator/_network/_hardware/nodes/test_acl.py b/tests/unit_tests/_primaite/_simulator/_network/_hardware/nodes/test_acl.py index 6eca0c44..79392d66 100644 --- a/tests/unit_tests/_primaite/_simulator/_network/_hardware/nodes/test_acl.py +++ b/tests/unit_tests/_primaite/_simulator/_network/_hardware/nodes/test_acl.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address import pytest diff --git a/tests/unit_tests/_primaite/_simulator/_network/_hardware/nodes/test_router.py b/tests/unit_tests/_primaite/_simulator/_network/_hardware/nodes/test_router.py index fe9387de..fe0c3a57 100644 --- a/tests/unit_tests/_primaite/_simulator/_network/_hardware/nodes/test_router.py +++ b/tests/unit_tests/_primaite/_simulator/_network/_hardware/nodes/test_router.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address from primaite.simulator.network.hardware.nodes.network.router import ACLAction, Router diff --git a/tests/unit_tests/_primaite/_simulator/_network/_hardware/nodes/test_switch.py b/tests/unit_tests/_primaite/_simulator/_network/_hardware/nodes/test_switch.py index 2613d536..e6bff60e 100644 --- a/tests/unit_tests/_primaite/_simulator/_network/_hardware/nodes/test_switch.py +++ b/tests/unit_tests/_primaite/_simulator/_network/_hardware/nodes/test_switch.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import pytest from primaite.simulator.network.hardware.node_operating_state import NodeOperatingState diff --git a/tests/unit_tests/_primaite/_simulator/_network/_hardware/test_network_interface_actions.py b/tests/unit_tests/_primaite/_simulator/_network/_hardware/test_network_interface_actions.py index f35cf171..5cff4407 100644 --- a/tests/unit_tests/_primaite/_simulator/_network/_hardware/test_network_interface_actions.py +++ b/tests/unit_tests/_primaite/_simulator/_network/_hardware/test_network_interface_actions.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import pytest from primaite.simulator.network.hardware.base import NetworkInterface, Node diff --git a/tests/unit_tests/_primaite/_simulator/_network/_hardware/test_nic.py b/tests/unit_tests/_primaite/_simulator/_network/_hardware/test_nic.py index 29d5ec67..f9ff0328 100644 --- a/tests/unit_tests/_primaite/_simulator/_network/_hardware/test_nic.py +++ b/tests/unit_tests/_primaite/_simulator/_network/_hardware/test_nic.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import re from ipaddress import IPv4Address 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 44c5c781..605f8c3b 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 @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import pytest from primaite.simulator.file_system.file import File diff --git a/tests/unit_tests/_primaite/_simulator/_network/_transmission/__init__.py b/tests/unit_tests/_primaite/_simulator/_network/_transmission/__init__.py index be6c00e7..836b79af 100644 --- a/tests/unit_tests/_primaite/_simulator/_network/_transmission/__init__.py +++ b/tests/unit_tests/_primaite/_simulator/_network/_transmission/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/tests/unit_tests/_primaite/_simulator/_network/_transmission/test_data_link_layer.py b/tests/unit_tests/_primaite/_simulator/_network/_transmission/test_data_link_layer.py index e7e425b1..161d9cb4 100644 --- a/tests/unit_tests/_primaite/_simulator/_network/_transmission/test_data_link_layer.py +++ b/tests/unit_tests/_primaite/_simulator/_network/_transmission/test_data_link_layer.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import pytest from primaite.simulator.network.protocols.icmp import ICMPPacket diff --git a/tests/unit_tests/_primaite/_simulator/_network/_transmission/test_network_layer.py b/tests/unit_tests/_primaite/_simulator/_network/_transmission/test_network_layer.py index 658726b5..990a0bbf 100644 --- a/tests/unit_tests/_primaite/_simulator/_network/_transmission/test_network_layer.py +++ b/tests/unit_tests/_primaite/_simulator/_network/_transmission/test_network_layer.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import pytest from primaite.simulator.network.protocols.icmp import ICMPPacket, ICMPType diff --git a/tests/unit_tests/_primaite/_simulator/_network/test_container.py b/tests/unit_tests/_primaite/_simulator/_network/test_container.py index f764f9b5..b1de710a 100644 --- a/tests/unit_tests/_primaite/_simulator/_network/test_container.py +++ b/tests/unit_tests/_primaite/_simulator/_network/test_container.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import json import pytest diff --git a/tests/unit_tests/_primaite/_simulator/_network/test_creation.py b/tests/unit_tests/_primaite/_simulator/_network/test_creation.py index 2e86ebbc..9885df67 100644 --- a/tests/unit_tests/_primaite/_simulator/_network/test_creation.py +++ b/tests/unit_tests/_primaite/_simulator/_network/test_creation.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import pytest from primaite.simulator.network.container import Network diff --git a/tests/unit_tests/_primaite/_simulator/_network/test_utils.py b/tests/unit_tests/_primaite/_simulator/_network/test_utils.py index c80189c1..d86aa876 100644 --- a/tests/unit_tests/_primaite/_simulator/_network/test_utils.py +++ b/tests/unit_tests/_primaite/_simulator/_network/test_utils.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from primaite.simulator.network.utils import convert_bytes_to_megabits, convert_megabits_to_bytes diff --git a/tests/unit_tests/_primaite/_simulator/_system/__init__.py b/tests/unit_tests/_primaite/_simulator/_system/__init__.py index be6c00e7..836b79af 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/__init__.py +++ b/tests/unit_tests/_primaite/_simulator/_system/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/tests/unit_tests/_primaite/_simulator/_system/_applications/__init__.py b/tests/unit_tests/_primaite/_simulator/_system/_applications/__init__.py index be6c00e7..836b79af 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/_applications/__init__.py +++ b/tests/unit_tests/_primaite/_simulator/_system/_applications/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/tests/unit_tests/_primaite/_simulator/_system/_applications/_red_applications/__init__.py b/tests/unit_tests/_primaite/_simulator/_system/_applications/_red_applications/__init__.py index be6c00e7..836b79af 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/_applications/_red_applications/__init__.py +++ b/tests/unit_tests/_primaite/_simulator/_system/_applications/_red_applications/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/tests/unit_tests/_primaite/_simulator/_system/_applications/_red_applications/test_c2_suite.py b/tests/unit_tests/_primaite/_simulator/_system/_applications/_red_applications/test_c2_suite.py index 12dddf67..4ff387ce 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/_applications/_red_applications/test_c2_suite.py +++ b/tests/unit_tests/_primaite/_simulator/_system/_applications/_red_applications/test_c2_suite.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import pytest from primaite.simulator.network.container import Network diff --git a/tests/unit_tests/_primaite/_simulator/_system/_applications/_red_applications/test_data_manipulation_bot.py b/tests/unit_tests/_primaite/_simulator/_system/_applications/_red_applications/test_data_manipulation_bot.py index 34a29cd0..6e9ee224 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/_applications/_red_applications/test_data_manipulation_bot.py +++ b/tests/unit_tests/_primaite/_simulator/_system/_applications/_red_applications/test_data_manipulation_bot.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import pytest from primaite.simulator.network.hardware.base import Node diff --git a/tests/unit_tests/_primaite/_simulator/_system/_applications/_red_applications/test_dos_bot.py b/tests/unit_tests/_primaite/_simulator/_system/_applications/_red_applications/test_dos_bot.py index e9762476..9d8b7809 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/_applications/_red_applications/test_dos_bot.py +++ b/tests/unit_tests/_primaite/_simulator/_system/_applications/_red_applications/test_dos_bot.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address import pytest diff --git a/tests/unit_tests/_primaite/_simulator/_system/_applications/test_application_actions.py b/tests/unit_tests/_primaite/_simulator/_system/_applications/test_application_actions.py index 0e9c536c..a69dc844 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/_applications/test_application_actions.py +++ b/tests/unit_tests/_primaite/_simulator/_system/_applications/test_application_actions.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from primaite.simulator.system.applications.application import Application, ApplicationOperatingState diff --git a/tests/unit_tests/_primaite/_simulator/_system/_applications/test_application_registry.py b/tests/unit_tests/_primaite/_simulator/_system/_applications/test_application_registry.py index f97e915e..16a4c9ad 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/_applications/test_application_registry.py +++ b/tests/unit_tests/_primaite/_simulator/_system/_applications/test_application_registry.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import pytest from primaite.simulator.system.applications.application import Application diff --git a/tests/unit_tests/_primaite/_simulator/_system/_applications/test_applications.py b/tests/unit_tests/_primaite/_simulator/_system/_applications/test_applications.py index aef5d6d1..dd29f18e 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/_applications/test_applications.py +++ b/tests/unit_tests/_primaite/_simulator/_system/_applications/test_applications.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from primaite.simulator.system.applications.application import ApplicationOperatingState from primaite.simulator.system.software import SoftwareHealthState diff --git a/tests/unit_tests/_primaite/_simulator/_system/_applications/test_database_client.py b/tests/unit_tests/_primaite/_simulator/_system/_applications/test_database_client.py index e456ed78..5917fde7 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/_applications/test_database_client.py +++ b/tests/unit_tests/_primaite/_simulator/_system/_applications/test_database_client.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address from typing import Tuple from uuid import uuid4 diff --git a/tests/unit_tests/_primaite/_simulator/_system/_applications/test_web_browser.py b/tests/unit_tests/_primaite/_simulator/_system/_applications/test_web_browser.py index f1be475a..f78b3261 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/_applications/test_web_browser.py +++ b/tests/unit_tests/_primaite/_simulator/_system/_applications/test_web_browser.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import pytest from primaite.simulator.network.hardware.node_operating_state import NodeOperatingState diff --git a/tests/unit_tests/_primaite/_simulator/_system/_services/__init__.py b/tests/unit_tests/_primaite/_simulator/_system/_services/__init__.py index be6c00e7..836b79af 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/_services/__init__.py +++ b/tests/unit_tests/_primaite/_simulator/_system/_services/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/tests/unit_tests/_primaite/_simulator/_system/_services/test_database.py b/tests/unit_tests/_primaite/_simulator/_system/_services/test_database.py index 9e7ab1d2..ef165c8f 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/_services/test_database.py +++ b/tests/unit_tests/_primaite/_simulator/_system/_services/test_database.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import pytest from primaite.simulator.network.hardware.base import Node diff --git a/tests/unit_tests/_primaite/_simulator/_system/_services/test_dns_client.py b/tests/unit_tests/_primaite/_simulator/_system/_services/test_dns_client.py index db7e8d58..1bc5b353 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/_services/test_dns_client.py +++ b/tests/unit_tests/_primaite/_simulator/_system/_services/test_dns_client.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address import pytest diff --git a/tests/unit_tests/_primaite/_simulator/_system/_services/test_dns_server.py b/tests/unit_tests/_primaite/_simulator/_system/_services/test_dns_server.py index c64602c0..3bc2b1a4 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/_services/test_dns_server.py +++ b/tests/unit_tests/_primaite/_simulator/_system/_services/test_dns_server.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address import pytest diff --git a/tests/unit_tests/_primaite/_simulator/_system/_services/test_ftp_client.py b/tests/unit_tests/_primaite/_simulator/_system/_services/test_ftp_client.py index 95788834..d3e679db 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/_services/test_ftp_client.py +++ b/tests/unit_tests/_primaite/_simulator/_system/_services/test_ftp_client.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address import pytest diff --git a/tests/unit_tests/_primaite/_simulator/_system/_services/test_ftp_server.py b/tests/unit_tests/_primaite/_simulator/_system/_services/test_ftp_server.py index 291cdede..37c3d019 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/_services/test_ftp_server.py +++ b/tests/unit_tests/_primaite/_simulator/_system/_services/test_ftp_server.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import pytest from primaite.simulator.file_system.file_system_item_abc import FileSystemItemHealthStatus diff --git a/tests/unit_tests/_primaite/_simulator/_system/_services/test_service_actions.py b/tests/unit_tests/_primaite/_simulator/_system/_services/test_service_actions.py index 537beb8b..60cd2422 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/_services/test_service_actions.py +++ b/tests/unit_tests/_primaite/_simulator/_system/_services/test_service_actions.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from primaite.simulator.system.services.service import Service, ServiceOperatingState from primaite.simulator.system.software import SoftwareHealthState diff --git a/tests/unit_tests/_primaite/_simulator/_system/_services/test_services.py b/tests/unit_tests/_primaite/_simulator/_system/_services/test_services.py index 8c12adaa..ad6fe135 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/_services/test_services.py +++ b/tests/unit_tests/_primaite/_simulator/_system/_services/test_services.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from uuid import uuid4 import pytest diff --git a/tests/unit_tests/_primaite/_simulator/_system/_services/test_terminal.py b/tests/unit_tests/_primaite/_simulator/_system/_services/test_terminal.py index 9b6a4bf3..08bef92d 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/_services/test_terminal.py +++ b/tests/unit_tests/_primaite/_simulator/_system/_services/test_terminal.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import Tuple from uuid import uuid4 diff --git a/tests/unit_tests/_primaite/_simulator/_system/_services/test_web_server.py b/tests/unit_tests/_primaite/_simulator/_system/_services/test_web_server.py index 54f86ec8..606a195c 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/_services/test_web_server.py +++ b/tests/unit_tests/_primaite/_simulator/_system/_services/test_web_server.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import pytest from primaite.simulator.network.hardware.node_operating_state import NodeOperatingState diff --git a/tests/unit_tests/_primaite/_simulator/_system/core/test_sys_log.py b/tests/unit_tests/_primaite/_simulator/_system/core/test_sys_log.py index 053211cd..5a734b6e 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/core/test_sys_log.py +++ b/tests/unit_tests/_primaite/_simulator/_system/core/test_sys_log.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from uuid import uuid4 import pytest diff --git a/tests/unit_tests/_primaite/_simulator/_system/test_software.py b/tests/unit_tests/_primaite/_simulator/_system/test_software.py index 300f8d9d..a203a636 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/test_software.py +++ b/tests/unit_tests/_primaite/_simulator/_system/test_software.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import Dict import pytest diff --git a/tests/unit_tests/_primaite/_simulator/test_core.py b/tests/unit_tests/_primaite/_simulator/test_core.py index 02960978..271375eb 100644 --- a/tests/unit_tests/_primaite/_simulator/test_core.py +++ b/tests/unit_tests/_primaite/_simulator/test_core.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import Callable, Dict, List, Literal, Tuple import pytest diff --git a/tests/unit_tests/_primaite/_simulator/test_sim_container.py b/tests/unit_tests/_primaite/_simulator/test_sim_container.py index fe702307..f482d7e6 100644 --- a/tests/unit_tests/_primaite/_simulator/test_sim_container.py +++ b/tests/unit_tests/_primaite/_simulator/test_sim_container.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from primaite.simulator.sim_container import Simulation diff --git a/tests/unit_tests/_primaite/_utils/__init__.py b/tests/unit_tests/_primaite/_utils/__init__.py index be6c00e7..836b79af 100644 --- a/tests/unit_tests/_primaite/_utils/__init__.py +++ b/tests/unit_tests/_primaite/_utils/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/tests/unit_tests/_primaite/_utils/_validation/__init__.py b/tests/unit_tests/_primaite/_utils/_validation/__init__.py index be6c00e7..836b79af 100644 --- a/tests/unit_tests/_primaite/_utils/_validation/__init__.py +++ b/tests/unit_tests/_primaite/_utils/_validation/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/tests/unit_tests/_primaite/_utils/_validation/test_ip_protocol.py b/tests/unit_tests/_primaite/_utils/_validation/test_ip_protocol.py index 27829570..7acbe4a7 100644 --- a/tests/unit_tests/_primaite/_utils/_validation/test_ip_protocol.py +++ b/tests/unit_tests/_primaite/_utils/_validation/test_ip_protocol.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import pytest from primaite.utils.validation.ip_protocol import IPProtocol, is_valid_protocol, PROTOCOL_LOOKUP, protocol_validator diff --git a/tests/unit_tests/_primaite/_utils/_validation/test_port.py b/tests/unit_tests/_primaite/_utils/_validation/test_port.py index 6a8a2429..2e30ab76 100644 --- a/tests/unit_tests/_primaite/_utils/_validation/test_port.py +++ b/tests/unit_tests/_primaite/_utils/_validation/test_port.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK import pytest from primaite.utils.validation.port import is_valid_port, Port, PORT_LOOKUP, port_validator diff --git a/tests/unit_tests/_primaite/_utils/test_dict_enum_keys_conversion.py b/tests/unit_tests/_primaite/_utils/test_dict_enum_keys_conversion.py index 1a1848ac..d0a64ece 100644 --- a/tests/unit_tests/_primaite/_utils/test_dict_enum_keys_conversion.py +++ b/tests/unit_tests/_primaite/_utils/test_dict_enum_keys_conversion.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from primaite.utils.converters import convert_dict_enum_keys_to_enum_values from primaite.utils.validation.ip_protocol import PROTOCOL_LOOKUP from primaite.utils.validation.port import PORT_LOOKUP From ab2dd6ca2788325d9bce3061bd6f63515c7ce618 Mon Sep 17 00:00:00 2001 From: Charlie Crane Date: Fri, 3 Jan 2025 14:41:45 +0000 Subject: [PATCH 53/58] Revert "#2912 - Actioning Review Comments" This reverts commit b11678a128183dbd1badaf663c2f57446f6258f3. --- benchmark/benchmark.py | 2 +- benchmark/primaite_benchmark.py | 2 +- benchmark/report.py | 2 +- benchmark/utils.py | 2 +- docs/_templates/custom-class-template.rst | 2 +- docs/_templates/custom-module-template.rst | 2 +- docs/api.rst | 2 +- docs/conf.py | 2 +- docs/index.rst | 2 +- docs/source/action_masking.rst | 2 +- docs/source/config.rst | 2 +- docs/source/configuration/agents.rst | 2 +- docs/source/configuration/game.rst | 2 +- docs/source/configuration/io_settings.rst | 2 +- docs/source/configuration/simulation.rst | 2 +- .../simulation/nodes/common/common.rst | 2 +- .../common/common_host_node_attributes.rst | 2 +- .../common/common_network_node_attributes.rst | 2 +- .../nodes/common/common_node_attributes.rst | 2 +- .../nodes/common/node_type_list.rst | 2 +- .../simulation/nodes/computer.rst | 2 +- .../simulation/nodes/firewall.rst | 2 +- .../simulation/nodes/network_examples.rst | 2 +- .../configuration/simulation/nodes/router.rst | 2 +- .../configuration/simulation/nodes/server.rst | 2 +- .../configuration/simulation/nodes/switch.rst | 2 +- .../simulation/software/applications.rst | 2 +- .../simulation/software/services.rst | 2 +- docs/source/customising_scenarios.rst | 2 +- docs/source/dependencies.rst | 2 +- docs/source/developer_tools.rst | 2 +- docs/source/environment.rst | 2 +- docs/source/example_notebooks.rst | 2 +- docs/source/game_layer.rst | 2 +- docs/source/getting_started.rst | 2 +- docs/source/glossary.rst | 2 +- .../how_to_guides/extensible_actions.rst | 2 +- docs/source/node_sets.rst | 2 +- docs/source/notebooks/executed_notebooks.rst | 2 +- docs/source/primaite-dependencies.rst | 2 +- docs/source/request_system.rst | 2 +- docs/source/rewards.rst | 2 +- docs/source/simulation.rst | 2 +- .../network/airspace.rst | 2 +- .../network/base_hardware.rst | 2 +- .../simulation_components/network/network.rst | 2 +- .../network/network_interfaces.rst | 2 +- .../network/nodes/firewall.rst | 2 +- .../network/nodes/host_node.rst | 2 +- .../network/nodes/network_node.rst | 2 +- .../network/nodes/router.rst | 2 +- .../network/nodes/switch.rst | 2 +- .../network/nodes/wireless_router.rst | 2 +- .../network/transport_to_data_link_layer.rst | 2 +- .../system/applications/c2_suite.rst | 2 +- .../applications/data_manipulation_bot.rst | 2 +- .../system/applications/database_client.rst | 2 +- .../system/applications/dos_bot.rst | 2 +- .../system/applications/nmap.rst | 2 +- .../system/applications/ransomware_script.rst | 2 +- .../system/applications/web_browser.rst | 2 +- .../system/common/common_configuration.rst | 2 +- .../system/common/db_payload_list.rst | 2 +- .../system/internal_frame_processing.rst | 2 +- .../system/list_of_applications.rst | 2 +- .../system/list_of_services.rst | 2 +- .../system/list_of_system_applications.rst | 2 +- .../system/list_of_system_services.rst | 2 +- .../simulation_components/system/pcap.rst | 2 +- .../system/services/database_service.rst | 2 +- .../system/services/dns_client.rst | 2 +- .../system/services/dns_server.rst | 2 +- .../system/services/ftp_client.rst | 2 +- .../system/services/ftp_server.rst | 2 +- .../system/services/ntp_client.rst | 2 +- .../system/services/ntp_server.rst | 2 +- .../system/services/terminal.rst | 2 +- .../system/services/web_server.rst | 2 +- .../system/session_and_software_manager.rst | 2 +- .../simulation_components/system/software.rst | 2 +- .../simulation_components/system/sys_log.rst | 2 +- docs/source/simulation_structure.rst | 2 +- docs/source/state_system.rst | 2 +- docs/source/varying_config_files.rst | 2 +- src/primaite/__init__.py | 2 +- src/primaite/_legacy/actions.py | 2 +- src/primaite/cli.py | 2 +- src/primaite/config/__init__.py | 2 +- src/primaite/config/load.py | 2 +- src/primaite/exceptions.py | 2 +- src/primaite/game/__init__.py | 2 +- src/primaite/game/agent/__init__.py | 2 +- src/primaite/game/agent/actions/__init__.py | 2 +- src/primaite/game/agent/actions/abstract.py | 2 +- src/primaite/game/agent/actions/acl.py | 2 +- .../game/agent/actions/application.py | 2 +- src/primaite/game/agent/actions/config.py | 2 +- src/primaite/game/agent/actions/file.py | 2 +- src/primaite/game/agent/actions/folder.py | 2 +- src/primaite/game/agent/actions/host_nic.py | 2 +- src/primaite/game/agent/actions/manager.py | 4 ++- src/primaite/game/agent/actions/network.py | 2 +- src/primaite/game/agent/actions/node.py | 2 +- src/primaite/game/agent/actions/service.py | 2 +- src/primaite/game/agent/actions/session.py | 2 +- src/primaite/game/agent/agent_log.py | 2 +- src/primaite/game/agent/interface.py | 2 +- .../game/agent/observations/__init__.py | 2 +- .../agent/observations/acl_observation.py | 2 +- .../observations/file_system_observations.py | 2 +- .../observations/firewall_observation.py | 2 +- .../agent/observations/host_observations.py | 2 +- .../agent/observations/link_observation.py | 2 +- .../agent/observations/nic_observations.py | 2 +- .../agent/observations/node_observations.py | 2 +- .../agent/observations/observation_manager.py | 2 +- .../game/agent/observations/observations.py | 2 +- .../agent/observations/router_observation.py | 2 +- .../observations/software_observation.py | 2 +- src/primaite/game/agent/rewards.py | 2 +- .../game/agent/scripted_agents/__init__.py | 2 +- .../scripted_agents/data_manipulation_bot.py | 2 +- .../scripted_agents/probabilistic_agent.py | 2 +- .../agent/scripted_agents/random_agent.py | 2 +- .../game/agent/scripted_agents/tap001.py | 2 +- src/primaite/game/agent/utils.py | 2 +- src/primaite/game/game.py | 2 +- src/primaite/game/science.py | 2 +- src/primaite/interface/__init__.py | 2 +- src/primaite/interface/request.py | 2 +- src/primaite/session/__init__.py | 2 +- src/primaite/session/environment.py | 2 +- src/primaite/session/episode_schedule.py | 2 +- src/primaite/session/io.py | 2 +- src/primaite/session/ray_envs.py | 2 +- src/primaite/setup/__init__.py | 2 +- src/primaite/setup/reset_demo_notebooks.py | 2 +- src/primaite/setup/reset_example_configs.py | 2 +- src/primaite/simulator/__init__.py | 2 +- src/primaite/simulator/core.py | 2 +- src/primaite/simulator/domain/__init__.py | 2 +- src/primaite/simulator/domain/account.py | 2 +- src/primaite/simulator/domain/controller.py | 2 +- .../simulator/file_system/__init__.py | 2 +- src/primaite/simulator/file_system/file.py | 2 +- .../simulator/file_system/file_system.py | 2 +- .../file_system/file_system_item_abc.py | 2 +- .../simulator/file_system/file_type.py | 2 +- src/primaite/simulator/file_system/folder.py | 2 +- src/primaite/simulator/network/__init__.py | 2 +- src/primaite/simulator/network/airspace.py | 2 +- src/primaite/simulator/network/container.py | 2 +- src/primaite/simulator/network/creation.py | 2 +- .../simulator/network/hardware/__init__.py | 2 +- .../simulator/network/hardware/base.py | 2 +- .../hardware/network_interface/__init__.py | 2 +- .../network_interface/wireless/__init__.py | 2 +- .../wireless/wireless_access_point.py | 2 +- .../wireless/wireless_nic.py | 2 +- .../network/hardware/node_operating_state.py | 2 +- .../network/hardware/nodes/__init__.py | 2 +- .../network/hardware/nodes/host/__init__.py | 2 +- .../network/hardware/nodes/host/computer.py | 2 +- .../network/hardware/nodes/host/host_node.py | 2 +- .../network/hardware/nodes/host/server.py | 2 +- .../hardware/nodes/network/__init__.py | 2 +- .../hardware/nodes/network/firewall.py | 2 +- .../hardware/nodes/network/network_node.py | 2 +- .../network/hardware/nodes/network/router.py | 2 +- .../network/hardware/nodes/network/switch.py | 2 +- .../hardware/nodes/network/wireless_router.py | 2 +- src/primaite/simulator/network/networks.py | 2 +- src/primaite/simulator/network/nmne.py | 2 +- .../simulator/network/protocols/__init__.py | 2 +- .../simulator/network/protocols/arp.py | 2 +- .../simulator/network/protocols/dns.py | 2 +- .../simulator/network/protocols/ftp.py | 2 +- .../simulator/network/protocols/http.py | 2 +- .../simulator/network/protocols/icmp.py | 2 +- .../simulator/network/protocols/masquerade.py | 2 +- .../simulator/network/protocols/ntp.py | 2 +- .../simulator/network/protocols/packet.py | 2 +- .../simulator/network/protocols/ssh.py | 2 +- .../network/transmission/__init__.py | 2 +- .../network/transmission/data_link_layer.py | 2 +- .../network/transmission/network_layer.py | 2 +- .../network/transmission/primaite_layer.py | 2 +- .../network/transmission/transport_layer.py | 2 +- src/primaite/simulator/network/utils.py | 2 +- src/primaite/simulator/sim_container.py | 2 +- src/primaite/simulator/system/__init__.py | 2 +- .../simulator/system/applications/__init__.py | 2 +- .../system/applications/application.py | 2 +- .../system/applications/database_client.py | 2 +- .../simulator/system/applications/nmap.py | 2 +- .../applications/red_applications/__init__.py | 2 +- .../red_applications/c2/__init__.py | 2 +- .../red_applications/c2/abstract_c2.py | 2 +- .../red_applications/c2/c2_beacon.py | 2 +- .../red_applications/c2/c2_server.py | 2 +- .../red_applications/data_manipulation_bot.py | 2 +- .../applications/red_applications/dos_bot.py | 2 +- .../red_applications/ransomware_script.py | 2 +- .../system/applications/web_browser.py | 2 +- .../simulator/system/core/__init__.py | 2 +- .../simulator/system/core/packet_capture.py | 2 +- .../simulator/system/core/session_manager.py | 2 +- .../simulator/system/core/software_manager.py | 2 +- src/primaite/simulator/system/core/sys_log.py | 2 +- .../simulator/system/processes/__init__.py | 2 +- .../simulator/system/processes/process.py | 2 +- .../simulator/system/services/__init__.py | 2 +- .../system/services/access/__init__.py | 2 +- .../system/services/access/user_manager.py | 2 +- .../services/access/user_session_manager.py | 2 +- .../simulator/system/services/arp/__init__.py | 2 +- .../simulator/system/services/arp/arp.py | 2 +- .../system/services/database/__init__.py | 2 +- .../services/database/database_service.py | 2 +- .../simulator/system/services/dns/__init__.py | 2 +- .../system/services/dns/dns_client.py | 2 +- .../system/services/dns/dns_server.py | 2 +- .../simulator/system/services/ftp/__init__.py | 2 +- .../system/services/ftp/ftp_client.py | 2 +- .../system/services/ftp/ftp_server.py | 2 +- .../system/services/ftp/ftp_service.py | 2 +- .../system/services/icmp/__init__.py | 2 +- .../simulator/system/services/icmp/icmp.py | 2 +- .../system/services/icmp/router_icmp.py | 2 +- .../simulator/system/services/ntp/__init__.py | 2 +- .../system/services/ntp/ntp_client.py | 2 +- .../system/services/ntp/ntp_server.py | 2 +- .../simulator/system/services/service.py | 2 +- .../system/services/terminal/__init__.py | 2 +- .../system/services/terminal/terminal.py | 2 +- .../system/services/web_server/__init__.py | 2 +- .../system/services/web_server/web_server.py | 2 +- src/primaite/simulator/system/software.py | 2 +- src/primaite/utils/__init__.py | 2 +- src/primaite/utils/cli/__init__.py | 2 +- src/primaite/utils/cli/dev_cli.py | 2 +- .../utils/cli/primaite_config_utils.py | 2 +- src/primaite/utils/converters.py | 2 +- src/primaite/utils/package_data.py | 2 +- src/primaite/utils/session_metadata_parser.py | 2 +- src/primaite/utils/session_output_reader.py | 2 +- src/primaite/utils/session_output_writer.py | 2 +- src/primaite/utils/validation/__init__.py | 2 +- src/primaite/utils/validation/ip_protocol.py | 2 +- src/primaite/utils/validation/ipv4_address.py | 2 +- src/primaite/utils/validation/port.py | 2 +- tests/__init__.py | 2 +- tests/conftest.py | 29 ++++++++++++++++++- tests/e2e_integration_tests/__init__.py | 2 +- .../action_masking/__init__.py | 2 +- .../test_agents_use_action_masks.py | 2 +- .../environments/__init__.py | 2 +- .../test_rllib_multi_agent_environment.py | 2 +- .../test_rllib_single_agent_environment.py | 2 +- .../environments/test_sb3_environment.py | 2 +- .../e2e_integration_tests/test_environment.py | 2 +- .../test_uc2_data_manipulation_scenario.py | 2 +- tests/integration_tests/__init__.py | 2 +- tests/integration_tests/cli/__init__.py | 2 +- tests/integration_tests/cli/test_dev_cli.py | 2 +- .../component_creation/__init__.py | 2 +- .../test_action_integration.py | 2 +- .../test_permission_system.py | 2 +- .../configuration_file_parsing/__init__.py | 2 +- .../nodes/__init__.py | 2 +- .../nodes/network/__init__.py | 2 +- .../nodes/network/test_firewall_config.py | 2 +- .../nodes/network/test_router_config.py | 2 +- .../nodes/test_node_config.py | 2 +- ...software_installation_and_configuration.py | 2 +- .../test_episode_scheduler.py | 2 +- .../test_game_options_config.py | 2 +- .../test_io_settings.py | 2 +- .../test_no_nodes_links_agents_config.py | 2 +- .../test_software_fix_duration.py | 2 +- .../applications/extended_application.py | 2 +- .../extensions/nodes/giga_switch.py | 2 +- .../extensions/nodes/super_computer.py | 2 +- .../extensions/services/extended_service.py | 2 +- .../extensions/test_extendable_config.py | 2 +- .../game_layer/actions/__init__.py | 2 +- .../test_application_request_permission.py | 2 +- .../actions/test_c2_suite_actions.py | 2 +- .../actions/test_configure_actions.py | 2 +- .../actions/test_file_request_permission.py | 2 +- .../actions/test_folder_request_permission.py | 2 +- .../actions/test_nic_request_permission.py | 2 +- .../actions/test_node_request_permission.py | 2 +- .../test_service_request_permission.py | 2 +- .../actions/test_terminal_actions.py | 2 +- .../game_layer/observations/__init__.py | 2 +- .../observations/test_acl_observations.py | 2 +- .../test_file_system_observations.py | 2 +- .../observations/test_firewall_observation.py | 2 +- .../observations/test_link_observations.py | 2 +- .../observations/test_nic_observations.py | 2 +- .../observations/test_node_observations.py | 2 +- .../observations/test_router_observation.py | 2 +- .../test_software_observations.py | 2 +- .../observations/test_user_observations.py | 2 +- .../game_layer/test_RNG_seed.py | 2 +- .../game_layer/test_action_mask.py | 2 +- .../game_layer/test_actions.py | 2 +- .../game_layer/test_observations.py | 2 +- .../game_layer/test_rewards.py | 2 +- tests/integration_tests/network/__init__.py | 2 +- .../network/test_airspace_config.py | 2 +- ...ndwidth_load_checks_before_transmission.py | 2 +- .../network/test_broadcast.py | 2 +- .../network/test_capture_nmne.py | 2 +- .../network/test_firewall.py | 2 +- .../network/test_frame_transmission.py | 2 +- ...test_multi_lan_internet_example_network.py | 2 +- .../network/test_network_creation.py | 2 +- .../network/test_nic_link_connection.py | 2 +- .../integration_tests/network/test_routing.py | 2 +- .../network/test_switched_network.py | 2 +- .../test_users_creation_from_config.py | 2 +- .../network/test_wireless_router.py | 2 +- tests/integration_tests/system/__init__.py | 2 +- .../test_c2_suite_integration.py | 2 +- .../test_data_manipulation_bot_and_server.py | 2 +- .../test_dos_bot_and_server.py | 2 +- .../test_ransomware_script.py | 2 +- .../system/test_application_on_node.py | 2 +- tests/integration_tests/system/test_arp.py | 2 +- .../system/test_database_on_node.py | 2 +- .../system/test_dns_client_server.py | 2 +- .../system/test_ftp_client_server.py | 2 +- tests/integration_tests/system/test_nmap.py | 2 +- .../system/test_ntp_client_server.py | 2 +- .../system/test_service_listening_on_ports.py | 2 +- .../system/test_service_on_node.py | 2 +- .../test_user_session_manager_logins.py | 2 +- .../system/test_web_client_server.py | 2 +- .../test_web_client_server_and_database.py | 2 +- .../test_simulation/__init__.py | 2 +- .../test_simulation/test_request_response.py | 2 +- tests/mock_and_patch/__init__.py | 2 +- tests/mock_and_patch/get_session_path_mock.py | 2 +- tests/unit_tests/__init__.py | 2 +- tests/unit_tests/_primaite/__init__.py | 2 +- tests/unit_tests/_primaite/_game/__init__.py | 2 +- .../_primaite/_game/_agent/__init__.py | 2 +- .../_primaite/_game/_agent/test_actions.py | 2 +- .../_primaite/_game/_agent/test_agent_log.py | 2 +- .../_game/_agent/test_observations.py | 2 +- .../_game/_agent/test_probabilistic_agent.py | 2 +- .../_game/_agent/test_sticky_rewards.py | 2 +- .../_primaite/_interface/__init__.py | 2 +- .../_primaite/_interface/test_request.py | 2 +- .../unit_tests/_primaite/_session/__init__.py | 2 +- .../_session/test_episode_schedule.py | 2 +- .../_primaite/_simulator/__init__.py | 2 +- .../_primaite/_simulator/_domain/__init__.py | 2 +- .../_simulator/_domain/test_account.py | 2 +- .../_simulator/_domain/test_controller.py | 2 +- .../_simulator/_file_system/__init__.py | 2 +- .../_simulator/_file_system/test_file.py | 2 +- .../_file_system/test_file_actions.py | 2 +- .../_file_system/test_file_system.py | 2 +- .../_file_system/test_file_system_actions.py | 2 +- .../_simulator/_file_system/test_folder.py | 2 +- .../_file_system/test_folder_actions.py | 2 +- .../_primaite/_simulator/_network/__init__.py | 2 +- .../_simulator/_network/_hardware/__init__.py | 2 +- .../_network/_hardware/nodes/__init__.py | 2 +- .../_network/_hardware/nodes/test_acl.py | 2 +- .../_network/_hardware/nodes/test_router.py | 2 +- .../_network/_hardware/nodes/test_switch.py | 2 +- .../test_network_interface_actions.py | 2 +- .../_simulator/_network/_hardware/test_nic.py | 2 +- .../_network/_hardware/test_node_actions.py | 2 +- .../_network/_transmission/__init__.py | 2 +- .../_transmission/test_data_link_layer.py | 2 +- .../_transmission/test_network_layer.py | 2 +- .../_simulator/_network/test_container.py | 2 +- .../_simulator/_network/test_creation.py | 2 +- .../_simulator/_network/test_utils.py | 2 +- .../_primaite/_simulator/_system/__init__.py | 2 +- .../_system/_applications/__init__.py | 2 +- .../_red_applications/__init__.py | 2 +- .../_red_applications/test_c2_suite.py | 2 +- .../test_data_manipulation_bot.py | 2 +- .../_red_applications/test_dos_bot.py | 2 +- .../_applications/test_application_actions.py | 2 +- .../test_application_registry.py | 2 +- .../_applications/test_applications.py | 2 +- .../_applications/test_database_client.py | 2 +- .../_system/_applications/test_web_browser.py | 2 +- .../_simulator/_system/_services/__init__.py | 2 +- .../_system/_services/test_database.py | 2 +- .../_system/_services/test_dns_client.py | 2 +- .../_system/_services/test_dns_server.py | 2 +- .../_system/_services/test_ftp_client.py | 2 +- .../_system/_services/test_ftp_server.py | 2 +- .../_system/_services/test_service_actions.py | 2 +- .../_system/_services/test_services.py | 2 +- .../_system/_services/test_terminal.py | 2 +- .../_system/_services/test_web_server.py | 2 +- .../_simulator/_system/core/test_sys_log.py | 2 +- .../_simulator/_system/test_software.py | 2 +- .../_primaite/_simulator/test_core.py | 2 +- .../_simulator/test_sim_container.py | 2 +- tests/unit_tests/_primaite/_utils/__init__.py | 2 +- .../_primaite/_utils/_validation/__init__.py | 2 +- .../_utils/_validation/test_ip_protocol.py | 2 +- .../_primaite/_utils/_validation/test_port.py | 2 +- .../_utils/test_dict_enum_keys_conversion.py | 2 +- 414 files changed, 443 insertions(+), 414 deletions(-) diff --git a/benchmark/benchmark.py b/benchmark/benchmark.py index ddedebb7..4ad398b9 100644 --- a/benchmark/benchmark.py +++ b/benchmark/benchmark.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import Any, Dict, Optional, Tuple from gymnasium.core import ObsType diff --git a/benchmark/primaite_benchmark.py b/benchmark/primaite_benchmark.py index 70ea8900..86ed22a9 100644 --- a/benchmark/primaite_benchmark.py +++ b/benchmark/primaite_benchmark.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import json import shutil from datetime import datetime diff --git a/benchmark/report.py b/benchmark/report.py index c11528ab..4035ceca 100644 --- a/benchmark/report.py +++ b/benchmark/report.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import json import sys from datetime import datetime diff --git a/benchmark/utils.py b/benchmark/utils.py index f17c64b7..2e92d80d 100644 --- a/benchmark/utils.py +++ b/benchmark/utils.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import platform from typing import Dict diff --git a/docs/_templates/custom-class-template.rst b/docs/_templates/custom-class-template.rst index 71e992bc..920158d5 100644 --- a/docs/_templates/custom-class-template.rst +++ b/docs/_templates/custom-class-template.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK .. Credit to https://github.com/JamesALeedham/Sphinx-Autosummary-Recursion for the custom templates. diff --git a/docs/_templates/custom-module-template.rst b/docs/_templates/custom-module-template.rst index 3a2ced35..98627e43 100644 --- a/docs/_templates/custom-module-template.rst +++ b/docs/_templates/custom-module-template.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK .. Credit to https://github.com/JamesALeedham/Sphinx-Autosummary-Recursion for the custom templates. diff --git a/docs/api.rst b/docs/api.rst index eb7e4719..977f9e87 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -2,7 +2,7 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK .. DO NOT DELETE THIS FILE! It contains the all-important `.. autosummary::` directive with `:recursive:` option, without diff --git a/docs/conf.py b/docs/conf.py index 60739499..318829fd 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK # Configuration file for the Sphinx documentation builder. # # For the full list of built-in configuration values, see the documentation: diff --git a/docs/index.rst b/docs/index.rst index 42cc1d6d..2ba43162 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK Welcome to PrimAITE's documentation ==================================== diff --git a/docs/source/action_masking.rst b/docs/source/action_masking.rst index dad6a484..264ab254 100644 --- a/docs/source/action_masking.rst +++ b/docs/source/action_masking.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK Action Masking ************** diff --git a/docs/source/config.rst b/docs/source/config.rst index 0fa4a4d5..eb0b9906 100644 --- a/docs/source/config.rst +++ b/docs/source/config.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK PrimAITE |VERSION| Configuration ******************************** diff --git a/docs/source/configuration/agents.rst b/docs/source/configuration/agents.rst index d11f7892..dece94c5 100644 --- a/docs/source/configuration/agents.rst +++ b/docs/source/configuration/agents.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK ``agents`` diff --git a/docs/source/configuration/game.rst b/docs/source/configuration/game.rst index b3c139b2..2048708c 100644 --- a/docs/source/configuration/game.rst +++ b/docs/source/configuration/game.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK ``game`` diff --git a/docs/source/configuration/io_settings.rst b/docs/source/configuration/io_settings.rst index ab3a978e..1c9585c9 100644 --- a/docs/source/configuration/io_settings.rst +++ b/docs/source/configuration/io_settings.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK ``io_settings`` diff --git a/docs/source/configuration/simulation.rst b/docs/source/configuration/simulation.rst index 0b2067d8..fa1d774a 100644 --- a/docs/source/configuration/simulation.rst +++ b/docs/source/configuration/simulation.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK ``simulation`` diff --git a/docs/source/configuration/simulation/nodes/common/common.rst b/docs/source/configuration/simulation/nodes/common/common.rst index c45eccf6..a0f2eb13 100644 --- a/docs/source/configuration/simulation/nodes/common/common.rst +++ b/docs/source/configuration/simulation/nodes/common/common.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK .. _Node Attributes: diff --git a/docs/source/configuration/simulation/nodes/common/common_host_node_attributes.rst b/docs/source/configuration/simulation/nodes/common/common_host_node_attributes.rst index b717340e..bb3b2a52 100644 --- a/docs/source/configuration/simulation/nodes/common/common_host_node_attributes.rst +++ b/docs/source/configuration/simulation/nodes/common/common_host_node_attributes.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK .. _common_host_node_attributes: diff --git a/docs/source/configuration/simulation/nodes/common/common_network_node_attributes.rst b/docs/source/configuration/simulation/nodes/common/common_network_node_attributes.rst index 035c7e55..d556e2dc 100644 --- a/docs/source/configuration/simulation/nodes/common/common_network_node_attributes.rst +++ b/docs/source/configuration/simulation/nodes/common/common_network_node_attributes.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK .. _common_network_node_attributes: diff --git a/docs/source/configuration/simulation/nodes/common/common_node_attributes.rst b/docs/source/configuration/simulation/nodes/common/common_node_attributes.rst index 542b817b..6a95911f 100644 --- a/docs/source/configuration/simulation/nodes/common/common_node_attributes.rst +++ b/docs/source/configuration/simulation/nodes/common/common_node_attributes.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK .. _common_node_attributes: diff --git a/docs/source/configuration/simulation/nodes/common/node_type_list.rst b/docs/source/configuration/simulation/nodes/common/node_type_list.rst index 21181019..1ec496d9 100644 --- a/docs/source/configuration/simulation/nodes/common/node_type_list.rst +++ b/docs/source/configuration/simulation/nodes/common/node_type_list.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK ``type`` -------- diff --git a/docs/source/configuration/simulation/nodes/computer.rst b/docs/source/configuration/simulation/nodes/computer.rst index 456d11a2..32e0b2b9 100644 --- a/docs/source/configuration/simulation/nodes/computer.rst +++ b/docs/source/configuration/simulation/nodes/computer.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK .. _computer_configuration: diff --git a/docs/source/configuration/simulation/nodes/firewall.rst b/docs/source/configuration/simulation/nodes/firewall.rst index 84b5c99e..775ffabd 100644 --- a/docs/source/configuration/simulation/nodes/firewall.rst +++ b/docs/source/configuration/simulation/nodes/firewall.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK .. _firewall_configuration: diff --git a/docs/source/configuration/simulation/nodes/network_examples.rst b/docs/source/configuration/simulation/nodes/network_examples.rst index 80e934e5..2a34a206 100644 --- a/docs/source/configuration/simulation/nodes/network_examples.rst +++ b/docs/source/configuration/simulation/nodes/network_examples.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK .. _network_examples: diff --git a/docs/source/configuration/simulation/nodes/router.rst b/docs/source/configuration/simulation/nodes/router.rst index 4b41784c..ac9d6411 100644 --- a/docs/source/configuration/simulation/nodes/router.rst +++ b/docs/source/configuration/simulation/nodes/router.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK .. _router_configuration: diff --git a/docs/source/configuration/simulation/nodes/server.rst b/docs/source/configuration/simulation/nodes/server.rst index 616efb38..92b33ca7 100644 --- a/docs/source/configuration/simulation/nodes/server.rst +++ b/docs/source/configuration/simulation/nodes/server.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK .. _server_configuration: diff --git a/docs/source/configuration/simulation/nodes/switch.rst b/docs/source/configuration/simulation/nodes/switch.rst index d09f5ba7..17cf76f9 100644 --- a/docs/source/configuration/simulation/nodes/switch.rst +++ b/docs/source/configuration/simulation/nodes/switch.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK .. _switch_configuration: diff --git a/docs/source/configuration/simulation/software/applications.rst b/docs/source/configuration/simulation/software/applications.rst index 9973a167..8c590d53 100644 --- a/docs/source/configuration/simulation/software/applications.rst +++ b/docs/source/configuration/simulation/software/applications.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK ``applications`` ---------------- diff --git a/docs/source/configuration/simulation/software/services.rst b/docs/source/configuration/simulation/software/services.rst index ec6bbba9..fafdf2e8 100644 --- a/docs/source/configuration/simulation/software/services.rst +++ b/docs/source/configuration/simulation/software/services.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK ``services`` ------------ diff --git a/docs/source/customising_scenarios.rst b/docs/source/customising_scenarios.rst index df7d4b1e..092f306b 100644 --- a/docs/source/customising_scenarios.rst +++ b/docs/source/customising_scenarios.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK Customising Agents ****************** diff --git a/docs/source/dependencies.rst b/docs/source/dependencies.rst index e8be00d3..74f3cd14 100644 --- a/docs/source/dependencies.rst +++ b/docs/source/dependencies.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK .. role:: raw-html(raw) :format: html diff --git a/docs/source/developer_tools.rst b/docs/source/developer_tools.rst index b3d81a27..a66b7902 100644 --- a/docs/source/developer_tools.rst +++ b/docs/source/developer_tools.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK .. _Developer Tools: diff --git a/docs/source/environment.rst b/docs/source/environment.rst index 251b1090..a282c09e 100644 --- a/docs/source/environment.rst +++ b/docs/source/environment.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK RL Environments *************** diff --git a/docs/source/example_notebooks.rst b/docs/source/example_notebooks.rst index 6caeae3d..920175c9 100644 --- a/docs/source/example_notebooks.rst +++ b/docs/source/example_notebooks.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK .. _example jupyter notebooks: diff --git a/docs/source/game_layer.rst b/docs/source/game_layer.rst index 58a274d9..775c02b5 100644 --- a/docs/source/game_layer.rst +++ b/docs/source/game_layer.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK PrimAITE Game layer ******************* diff --git a/docs/source/getting_started.rst b/docs/source/getting_started.rst index 427d1823..ded92c60 100644 --- a/docs/source/getting_started.rst +++ b/docs/source/getting_started.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK .. _getting-started: diff --git a/docs/source/glossary.rst b/docs/source/glossary.rst index 02c578d1..8fff0ea3 100644 --- a/docs/source/glossary.rst +++ b/docs/source/glossary.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK Glossary ============= diff --git a/docs/source/how_to_guides/extensible_actions.rst b/docs/source/how_to_guides/extensible_actions.rst index 1c44c2b2..6e44a905 100644 --- a/docs/source/how_to_guides/extensible_actions.rst +++ b/docs/source/how_to_guides/extensible_actions.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK .. _about: diff --git a/docs/source/node_sets.rst b/docs/source/node_sets.rst index 3c247478..866f0139 100644 --- a/docs/source/node_sets.rst +++ b/docs/source/node_sets.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK .. _network_node_adder: diff --git a/docs/source/notebooks/executed_notebooks.rst b/docs/source/notebooks/executed_notebooks.rst index f4acfad6..3431d344 100644 --- a/docs/source/notebooks/executed_notebooks.rst +++ b/docs/source/notebooks/executed_notebooks.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK .. _Executed Notebooks: diff --git a/docs/source/primaite-dependencies.rst b/docs/source/primaite-dependencies.rst index 14a96349..8367ee61 100644 --- a/docs/source/primaite-dependencies.rst +++ b/docs/source/primaite-dependencies.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +-------------------+---------+------------------------------------+-------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------+ | Name | Version | License | Description | URL | diff --git a/docs/source/request_system.rst b/docs/source/request_system.rst index b89d0906..f2d2e68d 100644 --- a/docs/source/request_system.rst +++ b/docs/source/request_system.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK Request System ************** diff --git a/docs/source/rewards.rst b/docs/source/rewards.rst index 254237ee..0163284c 100644 --- a/docs/source/rewards.rst +++ b/docs/source/rewards.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK Rewards ####### diff --git a/docs/source/simulation.rst b/docs/source/simulation.rst index 95807703..cc723e40 100644 --- a/docs/source/simulation.rst +++ b/docs/source/simulation.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK Simulation diff --git a/docs/source/simulation_components/network/airspace.rst b/docs/source/simulation_components/network/airspace.rst index a6967b91..06a884a7 100644 --- a/docs/source/simulation_components/network/airspace.rst +++ b/docs/source/simulation_components/network/airspace.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK .. _airspace: diff --git a/docs/source/simulation_components/network/base_hardware.rst b/docs/source/simulation_components/network/base_hardware.rst index 8b325ffc..ce1e5c74 100644 --- a/docs/source/simulation_components/network/base_hardware.rst +++ b/docs/source/simulation_components/network/base_hardware.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK ############# Base Hardware diff --git a/docs/source/simulation_components/network/network.rst b/docs/source/simulation_components/network/network.rst index 152b74b8..4cc121a3 100644 --- a/docs/source/simulation_components/network/network.rst +++ b/docs/source/simulation_components/network/network.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK .. _network: diff --git a/docs/source/simulation_components/network/network_interfaces.rst b/docs/source/simulation_components/network/network_interfaces.rst index 663af7ba..c6b97a8e 100644 --- a/docs/source/simulation_components/network/network_interfaces.rst +++ b/docs/source/simulation_components/network/network_interfaces.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK ################################# Network Interface Hierarchy Model diff --git a/docs/source/simulation_components/network/nodes/firewall.rst b/docs/source/simulation_components/network/nodes/firewall.rst index f2d7e61a..1ef16d63 100644 --- a/docs/source/simulation_components/network/nodes/firewall.rst +++ b/docs/source/simulation_components/network/nodes/firewall.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK ######## Firewall diff --git a/docs/source/simulation_components/network/nodes/host_node.rst b/docs/source/simulation_components/network/nodes/host_node.rst index 2c1e75d0..b8aae098 100644 --- a/docs/source/simulation_components/network/nodes/host_node.rst +++ b/docs/source/simulation_components/network/nodes/host_node.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK ######### diff --git a/docs/source/simulation_components/network/nodes/network_node.rst b/docs/source/simulation_components/network/nodes/network_node.rst index 4aebe09f..e1fa976c 100644 --- a/docs/source/simulation_components/network/nodes/network_node.rst +++ b/docs/source/simulation_components/network/nodes/network_node.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK ############ Network Node diff --git a/docs/source/simulation_components/network/nodes/router.rst b/docs/source/simulation_components/network/nodes/router.rst index fb582b23..5d3de60f 100644 --- a/docs/source/simulation_components/network/nodes/router.rst +++ b/docs/source/simulation_components/network/nodes/router.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK ###### Router diff --git a/docs/source/simulation_components/network/nodes/switch.rst b/docs/source/simulation_components/network/nodes/switch.rst index e7143f0c..0ecbcbf3 100644 --- a/docs/source/simulation_components/network/nodes/switch.rst +++ b/docs/source/simulation_components/network/nodes/switch.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK ###### Switch diff --git a/docs/source/simulation_components/network/nodes/wireless_router.rst b/docs/source/simulation_components/network/nodes/wireless_router.rst index d7207846..c0c245b2 100644 --- a/docs/source/simulation_components/network/nodes/wireless_router.rst +++ b/docs/source/simulation_components/network/nodes/wireless_router.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK ###### Wireless Router diff --git a/docs/source/simulation_components/network/transport_to_data_link_layer.rst b/docs/source/simulation_components/network/transport_to_data_link_layer.rst index 54118c90..02bfdcdc 100644 --- a/docs/source/simulation_components/network/transport_to_data_link_layer.rst +++ b/docs/source/simulation_components/network/transport_to_data_link_layer.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK Transport Layer to Data Link Layer ================================== diff --git a/docs/source/simulation_components/system/applications/c2_suite.rst b/docs/source/simulation_components/system/applications/c2_suite.rst index 3dd2b4fc..d045949a 100644 --- a/docs/source/simulation_components/system/applications/c2_suite.rst +++ b/docs/source/simulation_components/system/applications/c2_suite.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK .. _C2_Suite: diff --git a/docs/source/simulation_components/system/applications/data_manipulation_bot.rst b/docs/source/simulation_components/system/applications/data_manipulation_bot.rst index 91c33ede..1a387514 100644 --- a/docs/source/simulation_components/system/applications/data_manipulation_bot.rst +++ b/docs/source/simulation_components/system/applications/data_manipulation_bot.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK .. _DataManipulationBot: diff --git a/docs/source/simulation_components/system/applications/database_client.rst b/docs/source/simulation_components/system/applications/database_client.rst index 75a396b5..1fea78ab 100644 --- a/docs/source/simulation_components/system/applications/database_client.rst +++ b/docs/source/simulation_components/system/applications/database_client.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK .. _DatabaseClient: diff --git a/docs/source/simulation_components/system/applications/dos_bot.rst b/docs/source/simulation_components/system/applications/dos_bot.rst index 5c0ae86a..6ad45424 100644 --- a/docs/source/simulation_components/system/applications/dos_bot.rst +++ b/docs/source/simulation_components/system/applications/dos_bot.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK .. _DoSBot: diff --git a/docs/source/simulation_components/system/applications/nmap.rst b/docs/source/simulation_components/system/applications/nmap.rst index a82735c8..a5615a43 100644 --- a/docs/source/simulation_components/system/applications/nmap.rst +++ b/docs/source/simulation_components/system/applications/nmap.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK .. _NMAP: diff --git a/docs/source/simulation_components/system/applications/ransomware_script.rst b/docs/source/simulation_components/system/applications/ransomware_script.rst index b79ca802..5bff6991 100644 --- a/docs/source/simulation_components/system/applications/ransomware_script.rst +++ b/docs/source/simulation_components/system/applications/ransomware_script.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK .. _RansomwareScript: diff --git a/docs/source/simulation_components/system/applications/web_browser.rst b/docs/source/simulation_components/system/applications/web_browser.rst index 7062887b..c56c450d 100644 --- a/docs/source/simulation_components/system/applications/web_browser.rst +++ b/docs/source/simulation_components/system/applications/web_browser.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK .. _WebBrowser: diff --git a/docs/source/simulation_components/system/common/common_configuration.rst b/docs/source/simulation_components/system/common/common_configuration.rst index 411fd529..c53ac8b8 100644 --- a/docs/source/simulation_components/system/common/common_configuration.rst +++ b/docs/source/simulation_components/system/common/common_configuration.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK .. _Common Configuration: diff --git a/docs/source/simulation_components/system/common/db_payload_list.rst b/docs/source/simulation_components/system/common/db_payload_list.rst index 89668665..0930f09d 100644 --- a/docs/source/simulation_components/system/common/db_payload_list.rst +++ b/docs/source/simulation_components/system/common/db_payload_list.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK .. _Database Payload List: diff --git a/docs/source/simulation_components/system/internal_frame_processing.rst b/docs/source/simulation_components/system/internal_frame_processing.rst index f82dec13..65336f9b 100644 --- a/docs/source/simulation_components/system/internal_frame_processing.rst +++ b/docs/source/simulation_components/system/internal_frame_processing.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK .. _internal_frame_processing: diff --git a/docs/source/simulation_components/system/list_of_applications.rst b/docs/source/simulation_components/system/list_of_applications.rst index a7e05ea6..94090d93 100644 --- a/docs/source/simulation_components/system/list_of_applications.rst +++ b/docs/source/simulation_components/system/list_of_applications.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK .. toctree:: :maxdepth: 1 diff --git a/docs/source/simulation_components/system/list_of_services.rst b/docs/source/simulation_components/system/list_of_services.rst index 2082ac6f..b6995647 100644 --- a/docs/source/simulation_components/system/list_of_services.rst +++ b/docs/source/simulation_components/system/list_of_services.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK .. toctree:: :maxdepth: 1 diff --git a/docs/source/simulation_components/system/list_of_system_applications.rst b/docs/source/simulation_components/system/list_of_system_applications.rst index 0c66662f..c8807ef0 100644 --- a/docs/source/simulation_components/system/list_of_system_applications.rst +++ b/docs/source/simulation_components/system/list_of_system_applications.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK ``system applications`` """"""""""""""""""""""" diff --git a/docs/source/simulation_components/system/list_of_system_services.rst b/docs/source/simulation_components/system/list_of_system_services.rst index 01df4dc8..9b5c3265 100644 --- a/docs/source/simulation_components/system/list_of_system_services.rst +++ b/docs/source/simulation_components/system/list_of_system_services.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK ``system services`` """"""""""""""""""" diff --git a/docs/source/simulation_components/system/pcap.rst b/docs/source/simulation_components/system/pcap.rst index 0da28a39..830c28bd 100644 --- a/docs/source/simulation_components/system/pcap.rst +++ b/docs/source/simulation_components/system/pcap.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK PCAP ==== diff --git a/docs/source/simulation_components/system/services/database_service.rst b/docs/source/simulation_components/system/services/database_service.rst index b41c1097..f3e800cd 100644 --- a/docs/source/simulation_components/system/services/database_service.rst +++ b/docs/source/simulation_components/system/services/database_service.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK .. _DatabaseService: diff --git a/docs/source/simulation_components/system/services/dns_client.rst b/docs/source/simulation_components/system/services/dns_client.rst index 6475b4d4..eca152f0 100644 --- a/docs/source/simulation_components/system/services/dns_client.rst +++ b/docs/source/simulation_components/system/services/dns_client.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK .. _DNSClient: diff --git a/docs/source/simulation_components/system/services/dns_server.rst b/docs/source/simulation_components/system/services/dns_server.rst index 3d699048..1e30b9bd 100644 --- a/docs/source/simulation_components/system/services/dns_server.rst +++ b/docs/source/simulation_components/system/services/dns_server.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK .. _DNSServer: diff --git a/docs/source/simulation_components/system/services/ftp_client.rst b/docs/source/simulation_components/system/services/ftp_client.rst index 47566e5f..f9c7b4ce 100644 --- a/docs/source/simulation_components/system/services/ftp_client.rst +++ b/docs/source/simulation_components/system/services/ftp_client.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK .. _FTPClient: diff --git a/docs/source/simulation_components/system/services/ftp_server.rst b/docs/source/simulation_components/system/services/ftp_server.rst index e4cada29..f52fa043 100644 --- a/docs/source/simulation_components/system/services/ftp_server.rst +++ b/docs/source/simulation_components/system/services/ftp_server.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK .. _FTPServer: diff --git a/docs/source/simulation_components/system/services/ntp_client.rst b/docs/source/simulation_components/system/services/ntp_client.rst index fb965029..7af831bf 100644 --- a/docs/source/simulation_components/system/services/ntp_client.rst +++ b/docs/source/simulation_components/system/services/ntp_client.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK .. _NTPClient: diff --git a/docs/source/simulation_components/system/services/ntp_server.rst b/docs/source/simulation_components/system/services/ntp_server.rst index 68fadca9..a09c8bdd 100644 --- a/docs/source/simulation_components/system/services/ntp_server.rst +++ b/docs/source/simulation_components/system/services/ntp_server.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK .. _NTPServer: diff --git a/docs/source/simulation_components/system/services/terminal.rst b/docs/source/simulation_components/system/services/terminal.rst index bc5cee48..6909786e 100644 --- a/docs/source/simulation_components/system/services/terminal.rst +++ b/docs/source/simulation_components/system/services/terminal.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK .. _Terminal: diff --git a/docs/source/simulation_components/system/services/web_server.rst b/docs/source/simulation_components/system/services/web_server.rst index 011aa00f..cec20a60 100644 --- a/docs/source/simulation_components/system/services/web_server.rst +++ b/docs/source/simulation_components/system/services/web_server.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK .. _WebServer: diff --git a/docs/source/simulation_components/system/session_and_software_manager.rst b/docs/source/simulation_components/system/session_and_software_manager.rst index f20af556..230f6687 100644 --- a/docs/source/simulation_components/system/session_and_software_manager.rst +++ b/docs/source/simulation_components/system/session_and_software_manager.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK Session and Software Manager ============================ diff --git a/docs/source/simulation_components/system/software.rst b/docs/source/simulation_components/system/software.rst index d28815bb..c8f0e2d3 100644 --- a/docs/source/simulation_components/system/software.rst +++ b/docs/source/simulation_components/system/software.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK .. _software: diff --git a/docs/source/simulation_components/system/sys_log.rst b/docs/source/simulation_components/system/sys_log.rst index 05629993..cdf19faa 100644 --- a/docs/source/simulation_components/system/sys_log.rst +++ b/docs/source/simulation_components/system/sys_log.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK SysLog ====== diff --git a/docs/source/simulation_structure.rst b/docs/source/simulation_structure.rst index 7debe112..cd9ac409 100644 --- a/docs/source/simulation_structure.rst +++ b/docs/source/simulation_structure.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK Simulation Structure diff --git a/docs/source/state_system.rst b/docs/source/state_system.rst index a5fd1df1..e31474ea 100644 --- a/docs/source/state_system.rst +++ b/docs/source/state_system.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK Simulation State ================ diff --git a/docs/source/varying_config_files.rst b/docs/source/varying_config_files.rst index 942e522b..fa66f0d9 100644 --- a/docs/source/varying_config_files.rst +++ b/docs/source/varying_config_files.rst @@ -1,6 +1,6 @@ .. only:: comment - © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK Defining variations in the config files ======================================= diff --git a/src/primaite/__init__.py b/src/primaite/__init__.py index 54eac69d..8dd84428 100644 --- a/src/primaite/__init__.py +++ b/src/primaite/__init__.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import datetime as datetime import logging import logging.config diff --git a/src/primaite/_legacy/actions.py b/src/primaite/_legacy/actions.py index d2457a20..0eda7d86 100644 --- a/src/primaite/_legacy/actions.py +++ b/src/primaite/_legacy/actions.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK """ This module contains the ActionManager class which belongs to the Agent class. diff --git a/src/primaite/cli.py b/src/primaite/cli.py index 2bd18baf..4fbbdec9 100644 --- a/src/primaite/cli.py +++ b/src/primaite/cli.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK """Provides a CLI using Typer as an entry point.""" import logging import os diff --git a/src/primaite/config/__init__.py b/src/primaite/config/__init__.py index 7b5e2889..c2ae1b5b 100644 --- a/src/primaite/config/__init__.py +++ b/src/primaite/config/__init__.py @@ -1,2 +1,2 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK """Configuration parameters for running experiments.""" diff --git a/src/primaite/config/load.py b/src/primaite/config/load.py index 3553f527..39040d76 100644 --- a/src/primaite/config/load.py +++ b/src/primaite/config/load.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from pathlib import Path from typing import Dict, Final, Union diff --git a/src/primaite/exceptions.py b/src/primaite/exceptions.py index 4487111d..afc55271 100644 --- a/src/primaite/exceptions.py +++ b/src/primaite/exceptions.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK class PrimaiteError(Exception): """The root PrimAITE Error.""" diff --git a/src/primaite/game/__init__.py b/src/primaite/game/__init__.py index 57f96a56..39034e92 100644 --- a/src/primaite/game/__init__.py +++ b/src/primaite/game/__init__.py @@ -1,2 +1,2 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK """PrimAITE Game Layer.""" diff --git a/src/primaite/game/agent/__init__.py b/src/primaite/game/agent/__init__.py index 836b79af..be6c00e7 100644 --- a/src/primaite/game/agent/__init__.py +++ b/src/primaite/game/agent/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/src/primaite/game/agent/actions/__init__.py b/src/primaite/game/agent/actions/__init__.py index 1100e125..016a09ba 100644 --- a/src/primaite/game/agent/actions/__init__.py +++ b/src/primaite/game/agent/actions/__init__.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from primaite.game.agent.actions import ( abstract, diff --git a/src/primaite/game/agent/actions/abstract.py b/src/primaite/game/agent/actions/abstract.py index 15c9b4cb..8c332d5e 100644 --- a/src/primaite/game/agent/actions/abstract.py +++ b/src/primaite/game/agent/actions/abstract.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from __future__ import annotations from abc import ABC diff --git a/src/primaite/game/agent/actions/acl.py b/src/primaite/game/agent/actions/acl.py index 6fefeeda..d2846ddb 100644 --- a/src/primaite/game/agent/actions/acl.py +++ b/src/primaite/game/agent/actions/acl.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from __future__ import annotations from ipaddress import IPv4Address diff --git a/src/primaite/game/agent/actions/application.py b/src/primaite/game/agent/actions/application.py index 96609f93..91e34eae 100644 --- a/src/primaite/game/agent/actions/application.py +++ b/src/primaite/game/agent/actions/application.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import ClassVar from primaite.game.agent.actions.abstract import AbstractAction diff --git a/src/primaite/game/agent/actions/config.py b/src/primaite/game/agent/actions/config.py index 760e8dfa..050e9b94 100644 --- a/src/primaite/game/agent/actions/config.py +++ b/src/primaite/game/agent/actions/config.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import List, Optional, Union diff --git a/src/primaite/game/agent/actions/file.py b/src/primaite/game/agent/actions/file.py index e5ca1c46..b5e47c8a 100644 --- a/src/primaite/game/agent/actions/file.py +++ b/src/primaite/game/agent/actions/file.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import ClassVar from primaite.game.agent.actions.manager import AbstractAction diff --git a/src/primaite/game/agent/actions/folder.py b/src/primaite/game/agent/actions/folder.py index d1fd5ef1..a27ca89b 100644 --- a/src/primaite/game/agent/actions/folder.py +++ b/src/primaite/game/agent/actions/folder.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import ClassVar from primaite.game.agent.actions.manager import AbstractAction diff --git a/src/primaite/game/agent/actions/host_nic.py b/src/primaite/game/agent/actions/host_nic.py index 7b290103..e2adf7d7 100644 --- a/src/primaite/game/agent/actions/host_nic.py +++ b/src/primaite/game/agent/actions/host_nic.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import ClassVar from primaite.game.agent.actions.manager import AbstractAction diff --git a/src/primaite/game/agent/actions/manager.py b/src/primaite/game/agent/actions/manager.py index 625d8cec..a6a4f5a6 100644 --- a/src/primaite/game/agent/actions/manager.py +++ b/src/primaite/game/agent/actions/manager.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK """yaml example. agents: @@ -80,6 +80,8 @@ class ActionManager: self.action_map = {i: (a["action"], a["options"]) for i, a in act_map.items()} # make sure all numbers between 0 and N are represented as dict keys in action map assert all([i in self.action_map.keys() for i in range(len(self.action_map))]) + self.node_names: List[str] = [n["node_name"] for n in nodes] + """List of node names in this action space. The list order is the mapping between node index and node name.""" def get_action(self, action: int) -> Tuple[str, Dict]: """Produce action in CAOS format.""" diff --git a/src/primaite/game/agent/actions/network.py b/src/primaite/game/agent/actions/network.py index fa1c4451..346da9b7 100644 --- a/src/primaite/game/agent/actions/network.py +++ b/src/primaite/game/agent/actions/network.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import ClassVar diff --git a/src/primaite/game/agent/actions/node.py b/src/primaite/game/agent/actions/node.py index c6b74f2e..480cb8da 100644 --- a/src/primaite/game/agent/actions/node.py +++ b/src/primaite/game/agent/actions/node.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from abc import abstractmethod from typing import ClassVar, List, Optional, Union diff --git a/src/primaite/game/agent/actions/service.py b/src/primaite/game/agent/actions/service.py index fa47ffb1..7ccffb0a 100644 --- a/src/primaite/game/agent/actions/service.py +++ b/src/primaite/game/agent/actions/service.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import ClassVar from primaite.game.agent.actions.manager import AbstractAction diff --git a/src/primaite/game/agent/actions/session.py b/src/primaite/game/agent/actions/session.py index 1191987b..a0805a49 100644 --- a/src/primaite/game/agent/actions/session.py +++ b/src/primaite/game/agent/actions/session.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from abc import abstractmethod from primaite.game.agent.actions.manager import AbstractAction diff --git a/src/primaite/game/agent/agent_log.py b/src/primaite/game/agent/agent_log.py index 59fb4702..62ef4884 100644 --- a/src/primaite/game/agent/agent_log.py +++ b/src/primaite/game/agent/agent_log.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import logging from pathlib import Path diff --git a/src/primaite/game/agent/interface.py b/src/primaite/game/agent/interface.py index 4acc9108..14b97821 100644 --- a/src/primaite/game/agent/interface.py +++ b/src/primaite/game/agent/interface.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK """Interface for agents.""" from abc import ABC, abstractmethod from typing import Any, Dict, List, Optional, Tuple, TYPE_CHECKING diff --git a/src/primaite/game/agent/observations/__init__.py b/src/primaite/game/agent/observations/__init__.py index a38095b3..c4811c98 100644 --- a/src/primaite/game/agent/observations/__init__.py +++ b/src/primaite/game/agent/observations/__init__.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK # flake8: noqa # Pre-import all the observations when we load up the observations module so that they can be resolved by the parser. from primaite.game.agent.observations.acl_observation import ACLObservation diff --git a/src/primaite/game/agent/observations/acl_observation.py b/src/primaite/game/agent/observations/acl_observation.py index 86a6463a..41af5a8f 100644 --- a/src/primaite/game/agent/observations/acl_observation.py +++ b/src/primaite/game/agent/observations/acl_observation.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from __future__ import annotations from ipaddress import IPv4Address diff --git a/src/primaite/game/agent/observations/file_system_observations.py b/src/primaite/game/agent/observations/file_system_observations.py index 50ca93fd..1c73d026 100644 --- a/src/primaite/game/agent/observations/file_system_observations.py +++ b/src/primaite/game/agent/observations/file_system_observations.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from __future__ import annotations from typing import Dict, Iterable, List, Optional diff --git a/src/primaite/game/agent/observations/firewall_observation.py b/src/primaite/game/agent/observations/firewall_observation.py index a89ddfc5..42ceaff0 100644 --- a/src/primaite/game/agent/observations/firewall_observation.py +++ b/src/primaite/game/agent/observations/firewall_observation.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from __future__ import annotations from typing import Dict, List, Optional diff --git a/src/primaite/game/agent/observations/host_observations.py b/src/primaite/game/agent/observations/host_observations.py index 03e9aca1..617e8eee 100644 --- a/src/primaite/game/agent/observations/host_observations.py +++ b/src/primaite/game/agent/observations/host_observations.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from __future__ import annotations from typing import Dict, List, Optional diff --git a/src/primaite/game/agent/observations/link_observation.py b/src/primaite/game/agent/observations/link_observation.py index 851e9557..9af39a22 100644 --- a/src/primaite/game/agent/observations/link_observation.py +++ b/src/primaite/game/agent/observations/link_observation.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from __future__ import annotations from typing import Any, Dict, List diff --git a/src/primaite/game/agent/observations/nic_observations.py b/src/primaite/game/agent/observations/nic_observations.py index f87d2d76..d180b641 100644 --- a/src/primaite/game/agent/observations/nic_observations.py +++ b/src/primaite/game/agent/observations/nic_observations.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from __future__ import annotations from typing import Dict, List, Optional diff --git a/src/primaite/game/agent/observations/node_observations.py b/src/primaite/game/agent/observations/node_observations.py index 03869367..e11521b6 100644 --- a/src/primaite/game/agent/observations/node_observations.py +++ b/src/primaite/game/agent/observations/node_observations.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from __future__ import annotations from typing import Dict, List, Optional diff --git a/src/primaite/game/agent/observations/observation_manager.py b/src/primaite/game/agent/observations/observation_manager.py index 71a60433..9b20fdcb 100644 --- a/src/primaite/game/agent/observations/observation_manager.py +++ b/src/primaite/game/agent/observations/observation_manager.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from __future__ import annotations from typing import Any, Dict, List, Optional diff --git a/src/primaite/game/agent/observations/observations.py b/src/primaite/game/agent/observations/observations.py index 49b9ab72..a9663c56 100644 --- a/src/primaite/game/agent/observations/observations.py +++ b/src/primaite/game/agent/observations/observations.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK """Manages the observation space for the agent.""" from abc import ABC, abstractmethod from typing import Any, Dict, Iterable, Optional, Type, Union diff --git a/src/primaite/game/agent/observations/router_observation.py b/src/primaite/game/agent/observations/router_observation.py index ca455f4c..d064936a 100644 --- a/src/primaite/game/agent/observations/router_observation.py +++ b/src/primaite/game/agent/observations/router_observation.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from __future__ import annotations from typing import Dict, List, Optional diff --git a/src/primaite/game/agent/observations/software_observation.py b/src/primaite/game/agent/observations/software_observation.py index 37810c6e..15cd2447 100644 --- a/src/primaite/game/agent/observations/software_observation.py +++ b/src/primaite/game/agent/observations/software_observation.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from __future__ import annotations from typing import Dict diff --git a/src/primaite/game/agent/rewards.py b/src/primaite/game/agent/rewards.py index fead27f2..f528c851 100644 --- a/src/primaite/game/agent/rewards.py +++ b/src/primaite/game/agent/rewards.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK """ Manages the reward function for the agent. diff --git a/src/primaite/game/agent/scripted_agents/__init__.py b/src/primaite/game/agent/scripted_agents/__init__.py index 836b79af..be6c00e7 100644 --- a/src/primaite/game/agent/scripted_agents/__init__.py +++ b/src/primaite/game/agent/scripted_agents/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/src/primaite/game/agent/scripted_agents/data_manipulation_bot.py b/src/primaite/game/agent/scripted_agents/data_manipulation_bot.py index 2432dd7b..eb0ce957 100644 --- a/src/primaite/game/agent/scripted_agents/data_manipulation_bot.py +++ b/src/primaite/game/agent/scripted_agents/data_manipulation_bot.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import random from typing import Dict, Tuple diff --git a/src/primaite/game/agent/scripted_agents/probabilistic_agent.py b/src/primaite/game/agent/scripted_agents/probabilistic_agent.py index ce4d90d1..cd44644f 100644 --- a/src/primaite/game/agent/scripted_agents/probabilistic_agent.py +++ b/src/primaite/game/agent/scripted_agents/probabilistic_agent.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK """Agents with predefined behaviours.""" from typing import Dict, Optional, Tuple diff --git a/src/primaite/game/agent/scripted_agents/random_agent.py b/src/primaite/game/agent/scripted_agents/random_agent.py index 2c2ff091..eade3a0c 100644 --- a/src/primaite/game/agent/scripted_agents/random_agent.py +++ b/src/primaite/game/agent/scripted_agents/random_agent.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import random from typing import Dict, Optional, Tuple diff --git a/src/primaite/game/agent/scripted_agents/tap001.py b/src/primaite/game/agent/scripted_agents/tap001.py index 1ed200d7..6d370654 100644 --- a/src/primaite/game/agent/scripted_agents/tap001.py +++ b/src/primaite/game/agent/scripted_agents/tap001.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import random from typing import Dict, Tuple diff --git a/src/primaite/game/agent/utils.py b/src/primaite/game/agent/utils.py index 87b02858..15efd0b6 100644 --- a/src/primaite/game/agent/utils.py +++ b/src/primaite/game/agent/utils.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import Any, Dict, Hashable, Optional, Sequence NOT_PRESENT_IN_STATE = object() diff --git a/src/primaite/game/game.py b/src/primaite/game/game.py index 6555e272..c8fbac4e 100644 --- a/src/primaite/game/game.py +++ b/src/primaite/game/game.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK """PrimAITE game - Encapsulates the simulation and agents.""" from ipaddress import IPv4Address from typing import Dict, List, Optional, Union diff --git a/src/primaite/game/science.py b/src/primaite/game/science.py index 2cb5de7d..8d8949df 100644 --- a/src/primaite/game/science.py +++ b/src/primaite/game/science.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from random import random from typing import Any, Iterable, Mapping diff --git a/src/primaite/interface/__init__.py b/src/primaite/interface/__init__.py index 836b79af..be6c00e7 100644 --- a/src/primaite/interface/__init__.py +++ b/src/primaite/interface/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/src/primaite/interface/request.py b/src/primaite/interface/request.py index 03d6491e..1a9f0e5f 100644 --- a/src/primaite/interface/request.py +++ b/src/primaite/interface/request.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import Dict, ForwardRef, List, Literal, Union from pydantic import BaseModel, ConfigDict, StrictBool # , validate_call diff --git a/src/primaite/session/__init__.py b/src/primaite/session/__init__.py index 836b79af..be6c00e7 100644 --- a/src/primaite/session/__init__.py +++ b/src/primaite/session/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/src/primaite/session/environment.py b/src/primaite/session/environment.py index 8e608ede..c66663e3 100644 --- a/src/primaite/session/environment.py +++ b/src/primaite/session/environment.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import json import random import sys diff --git a/src/primaite/session/episode_schedule.py b/src/primaite/session/episode_schedule.py index 126dcf9f..ad4d38e9 100644 --- a/src/primaite/session/episode_schedule.py +++ b/src/primaite/session/episode_schedule.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK 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..78d7cb3c 100644 --- a/src/primaite/session/io.py +++ b/src/primaite/session/io.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK 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 33ba0540..33c74b0e 100644 --- a/src/primaite/session/ray_envs.py +++ b/src/primaite/session/ray_envs.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import json from typing import Dict, SupportsFloat, Tuple diff --git a/src/primaite/setup/__init__.py b/src/primaite/setup/__init__.py index 1447a47b..12e7c4e7 100644 --- a/src/primaite/setup/__init__.py +++ b/src/primaite/setup/__init__.py @@ -1,2 +1,2 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK """Utilities to prepare the user's data folders.""" diff --git a/src/primaite/setup/reset_demo_notebooks.py b/src/primaite/setup/reset_demo_notebooks.py index ad4091e3..f17fb211 100644 --- a/src/primaite/setup/reset_demo_notebooks.py +++ b/src/primaite/setup/reset_demo_notebooks.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK 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..c7eeecd5 100644 --- a/src/primaite/setup/reset_example_configs.py +++ b/src/primaite/setup/reset_example_configs.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import filecmp import os import shutil diff --git a/src/primaite/simulator/__init__.py b/src/primaite/simulator/__init__.py index e85a2d1e..ade1a73b 100644 --- a/src/primaite/simulator/__init__.py +++ b/src/primaite/simulator/__init__.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK """Warning: SIM_OUTPUT is a mutable global variable for the simulation output directory.""" from datetime import datetime from enum import IntEnum diff --git a/src/primaite/simulator/core.py b/src/primaite/simulator/core.py index 567a0493..848570fe 100644 --- a/src/primaite/simulator/core.py +++ b/src/primaite/simulator/core.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK # flake8: noqa """Core of the PrimAITE Simulator.""" import warnings diff --git a/src/primaite/simulator/domain/__init__.py b/src/primaite/simulator/domain/__init__.py index 836b79af..be6c00e7 100644 --- a/src/primaite/simulator/domain/__init__.py +++ b/src/primaite/simulator/domain/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/domain/account.py b/src/primaite/simulator/domain/account.py index 85ec6d46..d955cf55 100644 --- a/src/primaite/simulator/domain/account.py +++ b/src/primaite/simulator/domain/account.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK """User account simulation.""" from enum import Enum from typing import Dict diff --git a/src/primaite/simulator/domain/controller.py b/src/primaite/simulator/domain/controller.py index d8b7782c..a264ba24 100644 --- a/src/primaite/simulator/domain/controller.py +++ b/src/primaite/simulator/domain/controller.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from enum import Enum from typing import Dict, Final, List, Literal, Tuple diff --git a/src/primaite/simulator/file_system/__init__.py b/src/primaite/simulator/file_system/__init__.py index 836b79af..be6c00e7 100644 --- a/src/primaite/simulator/file_system/__init__.py +++ b/src/primaite/simulator/file_system/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/file_system/file.py b/src/primaite/simulator/file_system/file.py index 57d01ec9..ba39c791 100644 --- a/src/primaite/simulator/file_system/file.py +++ b/src/primaite/simulator/file_system/file.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK 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 8ff4b6fb..2162915f 100644 --- a/src/primaite/simulator/file_system/file_system.py +++ b/src/primaite/simulator/file_system/file_system.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from __future__ import annotations from pathlib import Path diff --git a/src/primaite/simulator/file_system/file_system_item_abc.py b/src/primaite/simulator/file_system/file_system_item_abc.py index 48b95d20..a9db8825 100644 --- a/src/primaite/simulator/file_system/file_system_item_abc.py +++ b/src/primaite/simulator/file_system/file_system_item_abc.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from __future__ import annotations import math diff --git a/src/primaite/simulator/file_system/file_type.py b/src/primaite/simulator/file_system/file_type.py index 343d3565..e6e81070 100644 --- a/src/primaite/simulator/file_system/file_type.py +++ b/src/primaite/simulator/file_system/file_type.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from __future__ import annotations from enum import Enum diff --git a/src/primaite/simulator/file_system/folder.py b/src/primaite/simulator/file_system/folder.py index ee0f3d01..c98e4492 100644 --- a/src/primaite/simulator/file_system/folder.py +++ b/src/primaite/simulator/file_system/folder.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from __future__ import annotations import warnings diff --git a/src/primaite/simulator/network/__init__.py b/src/primaite/simulator/network/__init__.py index 836b79af..be6c00e7 100644 --- a/src/primaite/simulator/network/__init__.py +++ b/src/primaite/simulator/network/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/network/airspace.py b/src/primaite/simulator/network/airspace.py index 1f6fe6b0..2b8503d6 100644 --- a/src/primaite/simulator/network/airspace.py +++ b/src/primaite/simulator/network/airspace.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from __future__ import annotations from abc import ABC, abstractmethod diff --git a/src/primaite/simulator/network/container.py b/src/primaite/simulator/network/container.py index bf677d5c..1082e172 100644 --- a/src/primaite/simulator/network/container.py +++ b/src/primaite/simulator/network/container.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address from typing import Any, Dict, List, Optional diff --git a/src/primaite/simulator/network/creation.py b/src/primaite/simulator/network/creation.py index 94c45428..5d36f58b 100644 --- a/src/primaite/simulator/network/creation.py +++ b/src/primaite/simulator/network/creation.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from abc import ABC, abstractmethod from ipaddress import IPv4Address from typing import Any, ClassVar, Dict, Literal, Type diff --git a/src/primaite/simulator/network/hardware/__init__.py b/src/primaite/simulator/network/hardware/__init__.py index 836b79af..be6c00e7 100644 --- a/src/primaite/simulator/network/hardware/__init__.py +++ b/src/primaite/simulator/network/hardware/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/network/hardware/base.py b/src/primaite/simulator/network/hardware/base.py index 8324715f..51e200e7 100644 --- a/src/primaite/simulator/network/hardware/base.py +++ b/src/primaite/simulator/network/hardware/base.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from __future__ import annotations import re diff --git a/src/primaite/simulator/network/hardware/network_interface/__init__.py b/src/primaite/simulator/network/hardware/network_interface/__init__.py index 836b79af..be6c00e7 100644 --- a/src/primaite/simulator/network/hardware/network_interface/__init__.py +++ b/src/primaite/simulator/network/hardware/network_interface/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/network/hardware/network_interface/wireless/__init__.py b/src/primaite/simulator/network/hardware/network_interface/wireless/__init__.py index 836b79af..be6c00e7 100644 --- a/src/primaite/simulator/network/hardware/network_interface/wireless/__init__.py +++ b/src/primaite/simulator/network/hardware/network_interface/wireless/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/network/hardware/network_interface/wireless/wireless_access_point.py b/src/primaite/simulator/network/hardware/network_interface/wireless/wireless_access_point.py index 3997872c..a9a31768 100644 --- a/src/primaite/simulator/network/hardware/network_interface/wireless/wireless_access_point.py +++ b/src/primaite/simulator/network/hardware/network_interface/wireless/wireless_access_point.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import Dict from primaite.simulator.network.hardware.base import ( diff --git a/src/primaite/simulator/network/hardware/network_interface/wireless/wireless_nic.py b/src/primaite/simulator/network/hardware/network_interface/wireless/wireless_nic.py index 9bc4cd6f..eebaedc5 100644 --- a/src/primaite/simulator/network/hardware/network_interface/wireless/wireless_nic.py +++ b/src/primaite/simulator/network/hardware/network_interface/wireless/wireless_nic.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import Dict from primaite.simulator.network.hardware.base import ( diff --git a/src/primaite/simulator/network/hardware/node_operating_state.py b/src/primaite/simulator/network/hardware/node_operating_state.py index 8771cb84..e64ef08b 100644 --- a/src/primaite/simulator/network/hardware/node_operating_state.py +++ b/src/primaite/simulator/network/hardware/node_operating_state.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from enum import Enum diff --git a/src/primaite/simulator/network/hardware/nodes/__init__.py b/src/primaite/simulator/network/hardware/nodes/__init__.py index 836b79af..be6c00e7 100644 --- a/src/primaite/simulator/network/hardware/nodes/__init__.py +++ b/src/primaite/simulator/network/hardware/nodes/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/network/hardware/nodes/host/__init__.py b/src/primaite/simulator/network/hardware/nodes/host/__init__.py index 836b79af..be6c00e7 100644 --- a/src/primaite/simulator/network/hardware/nodes/host/__init__.py +++ b/src/primaite/simulator/network/hardware/nodes/host/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/network/hardware/nodes/host/computer.py b/src/primaite/simulator/network/hardware/nodes/host/computer.py index 11b925b9..4253d15c 100644 --- a/src/primaite/simulator/network/hardware/nodes/host/computer.py +++ b/src/primaite/simulator/network/hardware/nodes/host/computer.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import ClassVar, Dict from primaite.simulator.network.hardware.nodes.host.host_node import HostNode diff --git a/src/primaite/simulator/network/hardware/nodes/host/host_node.py b/src/primaite/simulator/network/hardware/nodes/host/host_node.py index c51afbca..0c309136 100644 --- a/src/primaite/simulator/network/hardware/nodes/host/host_node.py +++ b/src/primaite/simulator/network/hardware/nodes/host/host_node.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from __future__ import annotations from ipaddress import IPv4Address diff --git a/src/primaite/simulator/network/hardware/nodes/host/server.py b/src/primaite/simulator/network/hardware/nodes/host/server.py index e16cfd8f..bf1ef39b 100644 --- a/src/primaite/simulator/network/hardware/nodes/host/server.py +++ b/src/primaite/simulator/network/hardware/nodes/host/server.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from primaite.simulator.network.hardware.nodes.host.host_node import HostNode diff --git a/src/primaite/simulator/network/hardware/nodes/network/__init__.py b/src/primaite/simulator/network/hardware/nodes/network/__init__.py index 836b79af..be6c00e7 100644 --- a/src/primaite/simulator/network/hardware/nodes/network/__init__.py +++ b/src/primaite/simulator/network/hardware/nodes/network/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/network/hardware/nodes/network/firewall.py b/src/primaite/simulator/network/hardware/nodes/network/firewall.py index f1ca4930..84cf8530 100644 --- a/src/primaite/simulator/network/hardware/nodes/network/firewall.py +++ b/src/primaite/simulator/network/hardware/nodes/network/firewall.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address from typing import Dict, Final, Union diff --git a/src/primaite/simulator/network/hardware/nodes/network/network_node.py b/src/primaite/simulator/network/hardware/nodes/network/network_node.py index 22ff2b28..a5b8544f 100644 --- a/src/primaite/simulator/network/hardware/nodes/network/network_node.py +++ b/src/primaite/simulator/network/hardware/nodes/network/network_node.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from abc import abstractmethod from typing import Optional diff --git a/src/primaite/simulator/network/hardware/nodes/network/router.py b/src/primaite/simulator/network/hardware/nodes/network/router.py index 4a049f99..e921faff 100644 --- a/src/primaite/simulator/network/hardware/nodes/network/router.py +++ b/src/primaite/simulator/network/hardware/nodes/network/router.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from __future__ import annotations import secrets diff --git a/src/primaite/simulator/network/hardware/nodes/network/switch.py b/src/primaite/simulator/network/hardware/nodes/network/switch.py index db923f1a..d29152a4 100644 --- a/src/primaite/simulator/network/hardware/nodes/network/switch.py +++ b/src/primaite/simulator/network/hardware/nodes/network/switch.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from __future__ import annotations from typing import Dict, Optional diff --git a/src/primaite/simulator/network/hardware/nodes/network/wireless_router.py b/src/primaite/simulator/network/hardware/nodes/network/wireless_router.py index 804a570e..aed314d2 100644 --- a/src/primaite/simulator/network/hardware/nodes/network/wireless_router.py +++ b/src/primaite/simulator/network/hardware/nodes/network/wireless_router.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address from typing import Any, Dict, Optional, Union diff --git a/src/primaite/simulator/network/networks.py b/src/primaite/simulator/network/networks.py index c840748e..2c3c15b4 100644 --- a/src/primaite/simulator/network/networks.py +++ b/src/primaite/simulator/network/networks.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address import yaml diff --git a/src/primaite/simulator/network/nmne.py b/src/primaite/simulator/network/nmne.py index a2e5f1fe..c9cff5de 100644 --- a/src/primaite/simulator/network/nmne.py +++ b/src/primaite/simulator/network/nmne.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import List from pydantic import BaseModel, ConfigDict diff --git a/src/primaite/simulator/network/protocols/__init__.py b/src/primaite/simulator/network/protocols/__init__.py index 836b79af..be6c00e7 100644 --- a/src/primaite/simulator/network/protocols/__init__.py +++ b/src/primaite/simulator/network/protocols/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/network/protocols/arp.py b/src/primaite/simulator/network/protocols/arp.py index 86e461d0..9e7f7ebe 100644 --- a/src/primaite/simulator/network/protocols/arp.py +++ b/src/primaite/simulator/network/protocols/arp.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from __future__ import annotations from ipaddress import IPv4Address diff --git a/src/primaite/simulator/network/protocols/dns.py b/src/primaite/simulator/network/protocols/dns.py index c0fed1aa..eb7b74ad 100644 --- a/src/primaite/simulator/network/protocols/dns.py +++ b/src/primaite/simulator/network/protocols/dns.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from __future__ import annotations from ipaddress import IPv4Address diff --git a/src/primaite/simulator/network/protocols/ftp.py b/src/primaite/simulator/network/protocols/ftp.py index fd8fdd2b..c570a634 100644 --- a/src/primaite/simulator/network/protocols/ftp.py +++ b/src/primaite/simulator/network/protocols/ftp.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from enum import Enum from typing import Any, Optional, Union diff --git a/src/primaite/simulator/network/protocols/http.py b/src/primaite/simulator/network/protocols/http.py index 54abdd98..5390cd26 100644 --- a/src/primaite/simulator/network/protocols/http.py +++ b/src/primaite/simulator/network/protocols/http.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from enum import Enum, IntEnum from primaite.simulator.network.protocols.packet import DataPacket diff --git a/src/primaite/simulator/network/protocols/icmp.py b/src/primaite/simulator/network/protocols/icmp.py index fcbe15da..9f0626f0 100644 --- a/src/primaite/simulator/network/protocols/icmp.py +++ b/src/primaite/simulator/network/protocols/icmp.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import secrets from enum import Enum from typing import Union diff --git a/src/primaite/simulator/network/protocols/masquerade.py b/src/primaite/simulator/network/protocols/masquerade.py index e0ed26b7..5c5f03b2 100644 --- a/src/primaite/simulator/network/protocols/masquerade.py +++ b/src/primaite/simulator/network/protocols/masquerade.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from enum import Enum from typing import Optional diff --git a/src/primaite/simulator/network/protocols/ntp.py b/src/primaite/simulator/network/protocols/ntp.py index c9b6f877..74e02dab 100644 --- a/src/primaite/simulator/network/protocols/ntp.py +++ b/src/primaite/simulator/network/protocols/ntp.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from __future__ import annotations from datetime import datetime diff --git a/src/primaite/simulator/network/protocols/packet.py b/src/primaite/simulator/network/protocols/packet.py index 6f28f716..7eeec13b 100644 --- a/src/primaite/simulator/network/protocols/packet.py +++ b/src/primaite/simulator/network/protocols/packet.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import Any from pydantic import BaseModel diff --git a/src/primaite/simulator/network/protocols/ssh.py b/src/primaite/simulator/network/protocols/ssh.py index 03411fb5..be7f842f 100644 --- a/src/primaite/simulator/network/protocols/ssh.py +++ b/src/primaite/simulator/network/protocols/ssh.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from enum import IntEnum from typing import Optional diff --git a/src/primaite/simulator/network/transmission/__init__.py b/src/primaite/simulator/network/transmission/__init__.py index 836b79af..be6c00e7 100644 --- a/src/primaite/simulator/network/transmission/__init__.py +++ b/src/primaite/simulator/network/transmission/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/network/transmission/data_link_layer.py b/src/primaite/simulator/network/transmission/data_link_layer.py index e7c2a124..259d62e3 100644 --- a/src/primaite/simulator/network/transmission/data_link_layer.py +++ b/src/primaite/simulator/network/transmission/data_link_layer.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from datetime import datetime from typing import Any, Optional diff --git a/src/primaite/simulator/network/transmission/network_layer.py b/src/primaite/simulator/network/transmission/network_layer.py index 7a6b34c9..49dcd1f5 100644 --- a/src/primaite/simulator/network/transmission/network_layer.py +++ b/src/primaite/simulator/network/transmission/network_layer.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from enum import Enum from pydantic import BaseModel diff --git a/src/primaite/simulator/network/transmission/primaite_layer.py b/src/primaite/simulator/network/transmission/primaite_layer.py index 8ff4ac02..981b6fbc 100644 --- a/src/primaite/simulator/network/transmission/primaite_layer.py +++ b/src/primaite/simulator/network/transmission/primaite_layer.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from enum import Enum from pydantic import BaseModel diff --git a/src/primaite/simulator/network/transmission/transport_layer.py b/src/primaite/simulator/network/transmission/transport_layer.py index 689eea2f..10cf802c 100644 --- a/src/primaite/simulator/network/transmission/transport_layer.py +++ b/src/primaite/simulator/network/transmission/transport_layer.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from enum import Enum from typing import List diff --git a/src/primaite/simulator/network/utils.py b/src/primaite/simulator/network/utils.py index b4d6c815..4fd1834a 100644 --- a/src/primaite/simulator/network/utils.py +++ b/src/primaite/simulator/network/utils.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import Union diff --git a/src/primaite/simulator/sim_container.py b/src/primaite/simulator/sim_container.py index 2a1deef4..809b52db 100644 --- a/src/primaite/simulator/sim_container.py +++ b/src/primaite/simulator/sim_container.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK 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..be6c00e7 100644 --- a/src/primaite/simulator/system/__init__.py +++ b/src/primaite/simulator/system/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/system/applications/__init__.py b/src/primaite/simulator/system/applications/__init__.py index 836b79af..be6c00e7 100644 --- a/src/primaite/simulator/system/applications/__init__.py +++ b/src/primaite/simulator/system/applications/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/system/applications/application.py b/src/primaite/simulator/system/applications/application.py index 1752c09a..a7871315 100644 --- a/src/primaite/simulator/system/applications/application.py +++ b/src/primaite/simulator/system/applications/application.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from __future__ import annotations from abc import abstractmethod diff --git a/src/primaite/simulator/system/applications/database_client.py b/src/primaite/simulator/system/applications/database_client.py index 840214f3..cd4b2a03 100644 --- a/src/primaite/simulator/system/applications/database_client.py +++ b/src/primaite/simulator/system/applications/database_client.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from __future__ import annotations from ipaddress import IPv4Address diff --git a/src/primaite/simulator/system/applications/nmap.py b/src/primaite/simulator/system/applications/nmap.py index f064eae3..e2b9117d 100644 --- a/src/primaite/simulator/system/applications/nmap.py +++ b/src/primaite/simulator/system/applications/nmap.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address, IPv4Network from typing import Any, Dict, Final, List, Optional, Set, Tuple, Union diff --git a/src/primaite/simulator/system/applications/red_applications/__init__.py b/src/primaite/simulator/system/applications/red_applications/__init__.py index 836b79af..be6c00e7 100644 --- a/src/primaite/simulator/system/applications/red_applications/__init__.py +++ b/src/primaite/simulator/system/applications/red_applications/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/system/applications/red_applications/c2/__init__.py b/src/primaite/simulator/system/applications/red_applications/c2/__init__.py index 33cc555f..60e39743 100644 --- a/src/primaite/simulator/system/applications/red_applications/c2/__init__.py +++ b/src/primaite/simulator/system/applications/red_applications/c2/__init__.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import Optional, Union from pydantic import BaseModel, Field, field_validator, ValidationInfo diff --git a/src/primaite/simulator/system/applications/red_applications/c2/abstract_c2.py b/src/primaite/simulator/system/applications/red_applications/c2/abstract_c2.py index 4cd54d69..f77bc33a 100644 --- a/src/primaite/simulator/system/applications/red_applications/c2/abstract_c2.py +++ b/src/primaite/simulator/system/applications/red_applications/c2/abstract_c2.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from abc import abstractmethod from enum import Enum from ipaddress import IPv4Address diff --git a/src/primaite/simulator/system/applications/red_applications/c2/c2_beacon.py b/src/primaite/simulator/system/applications/red_applications/c2/c2_beacon.py index b25eea6e..c0c3d872 100644 --- a/src/primaite/simulator/system/applications/red_applications/c2/c2_beacon.py +++ b/src/primaite/simulator/system/applications/red_applications/c2/c2_beacon.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address from typing import Dict, Optional diff --git a/src/primaite/simulator/system/applications/red_applications/c2/c2_server.py b/src/primaite/simulator/system/applications/red_applications/c2/c2_server.py index 654b86e7..f948d696 100644 --- a/src/primaite/simulator/system/applications/red_applications/c2/c2_server.py +++ b/src/primaite/simulator/system/applications/red_applications/c2/c2_server.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import Dict, Optional from prettytable import MARKDOWN, PrettyTable diff --git a/src/primaite/simulator/system/applications/red_applications/data_manipulation_bot.py b/src/primaite/simulator/system/applications/red_applications/data_manipulation_bot.py index 0423087e..9fdbae57 100644 --- a/src/primaite/simulator/system/applications/red_applications/data_manipulation_bot.py +++ b/src/primaite/simulator/system/applications/red_applications/data_manipulation_bot.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from enum import IntEnum from ipaddress import IPv4Address from typing import Dict, Optional 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 99c4acb3..fb2c8847 100644 --- a/src/primaite/simulator/system/applications/red_applications/dos_bot.py +++ b/src/primaite/simulator/system/applications/red_applications/dos_bot.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from enum import IntEnum from ipaddress import IPv4Address from typing import Dict, Optional diff --git a/src/primaite/simulator/system/applications/red_applications/ransomware_script.py b/src/primaite/simulator/system/applications/red_applications/ransomware_script.py index 3a8ac5ae..93b4c50d 100644 --- a/src/primaite/simulator/system/applications/red_applications/ransomware_script.py +++ b/src/primaite/simulator/system/applications/red_applications/ransomware_script.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address from typing import Dict, Optional diff --git a/src/primaite/simulator/system/applications/web_browser.py b/src/primaite/simulator/system/applications/web_browser.py index ff185e2a..c57a9bd3 100644 --- a/src/primaite/simulator/system/applications/web_browser.py +++ b/src/primaite/simulator/system/applications/web_browser.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from enum import Enum from ipaddress import IPv4Address from typing import Dict, List, Optional diff --git a/src/primaite/simulator/system/core/__init__.py b/src/primaite/simulator/system/core/__init__.py index 836b79af..be6c00e7 100644 --- a/src/primaite/simulator/system/core/__init__.py +++ b/src/primaite/simulator/system/core/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/system/core/packet_capture.py b/src/primaite/simulator/system/core/packet_capture.py index 813c288e..ea8b00a5 100644 --- a/src/primaite/simulator/system/core/packet_capture.py +++ b/src/primaite/simulator/system/core/packet_capture.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import json import logging from pathlib import Path diff --git a/src/primaite/simulator/system/core/session_manager.py b/src/primaite/simulator/system/core/session_manager.py index 48f1f383..75322e86 100644 --- a/src/primaite/simulator/system/core/session_manager.py +++ b/src/primaite/simulator/system/core/session_manager.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from __future__ import annotations from ipaddress import IPv4Address, IPv4Network diff --git a/src/primaite/simulator/system/core/software_manager.py b/src/primaite/simulator/system/core/software_manager.py index 5e63f2ec..2f19a8b0 100644 --- a/src/primaite/simulator/system/core/software_manager.py +++ b/src/primaite/simulator/system/core/software_manager.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from copy import deepcopy from ipaddress import IPv4Address, IPv4Network from typing import Any, Dict, List, Optional, Tuple, TYPE_CHECKING, Union diff --git a/src/primaite/simulator/system/core/sys_log.py b/src/primaite/simulator/system/core/sys_log.py index 741e5d33..9e22696d 100644 --- a/src/primaite/simulator/system/core/sys_log.py +++ b/src/primaite/simulator/system/core/sys_log.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import logging from pathlib import Path diff --git a/src/primaite/simulator/system/processes/__init__.py b/src/primaite/simulator/system/processes/__init__.py index 836b79af..be6c00e7 100644 --- a/src/primaite/simulator/system/processes/__init__.py +++ b/src/primaite/simulator/system/processes/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/system/processes/process.py b/src/primaite/simulator/system/processes/process.py index ad2babc1..225505c8 100644 --- a/src/primaite/simulator/system/processes/process.py +++ b/src/primaite/simulator/system/processes/process.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from abc import abstractmethod from enum import Enum from typing import Dict diff --git a/src/primaite/simulator/system/services/__init__.py b/src/primaite/simulator/system/services/__init__.py index 836b79af..be6c00e7 100644 --- a/src/primaite/simulator/system/services/__init__.py +++ b/src/primaite/simulator/system/services/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/system/services/access/__init__.py b/src/primaite/simulator/system/services/access/__init__.py index 836b79af..be6c00e7 100644 --- a/src/primaite/simulator/system/services/access/__init__.py +++ b/src/primaite/simulator/system/services/access/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/system/services/access/user_manager.py b/src/primaite/simulator/system/services/access/user_manager.py index 836b79af..be6c00e7 100644 --- a/src/primaite/simulator/system/services/access/user_manager.py +++ b/src/primaite/simulator/system/services/access/user_manager.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/system/services/access/user_session_manager.py b/src/primaite/simulator/system/services/access/user_session_manager.py index 836b79af..be6c00e7 100644 --- a/src/primaite/simulator/system/services/access/user_session_manager.py +++ b/src/primaite/simulator/system/services/access/user_session_manager.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/system/services/arp/__init__.py b/src/primaite/simulator/system/services/arp/__init__.py index 836b79af..be6c00e7 100644 --- a/src/primaite/simulator/system/services/arp/__init__.py +++ b/src/primaite/simulator/system/services/arp/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/system/services/arp/arp.py b/src/primaite/simulator/system/services/arp/arp.py index 31938e83..816eb99e 100644 --- a/src/primaite/simulator/system/services/arp/arp.py +++ b/src/primaite/simulator/system/services/arp/arp.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK 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..be6c00e7 100644 --- a/src/primaite/simulator/system/services/database/__init__.py +++ b/src/primaite/simulator/system/services/database/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/system/services/database/database_service.py b/src/primaite/simulator/system/services/database/database_service.py index 3a5f5b31..b7cd8886 100644 --- a/src/primaite/simulator/system/services/database/database_service.py +++ b/src/primaite/simulator/system/services/database/database_service.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK 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..be6c00e7 100644 --- a/src/primaite/simulator/system/services/dns/__init__.py +++ b/src/primaite/simulator/system/services/dns/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/system/services/dns/dns_client.py b/src/primaite/simulator/system/services/dns/dns_client.py index 02cf54ae..78642fa6 100644 --- a/src/primaite/simulator/system/services/dns/dns_client.py +++ b/src/primaite/simulator/system/services/dns/dns_client.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address from typing import Dict, Optional diff --git a/src/primaite/simulator/system/services/dns/dns_server.py b/src/primaite/simulator/system/services/dns/dns_server.py index b7c9a42c..5b380320 100644 --- a/src/primaite/simulator/system/services/dns/dns_server.py +++ b/src/primaite/simulator/system/services/dns/dns_server.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK 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..be6c00e7 100644 --- a/src/primaite/simulator/system/services/ftp/__init__.py +++ b/src/primaite/simulator/system/services/ftp/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/system/services/ftp/ftp_client.py b/src/primaite/simulator/system/services/ftp/ftp_client.py index 9c7b91ce..00b70332 100644 --- a/src/primaite/simulator/system/services/ftp/ftp_client.py +++ b/src/primaite/simulator/system/services/ftp/ftp_client.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address from typing import Dict, Optional diff --git a/src/primaite/simulator/system/services/ftp/ftp_server.py b/src/primaite/simulator/system/services/ftp/ftp_server.py index 9ce7d658..671200f5 100644 --- a/src/primaite/simulator/system/services/ftp/ftp_server.py +++ b/src/primaite/simulator/system/services/ftp/ftp_server.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import Any, Optional from primaite import getLogger diff --git a/src/primaite/simulator/system/services/ftp/ftp_service.py b/src/primaite/simulator/system/services/ftp/ftp_service.py index 52f451e1..77d82997 100644 --- a/src/primaite/simulator/system/services/ftp/ftp_service.py +++ b/src/primaite/simulator/system/services/ftp/ftp_service.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from abc import ABC from ipaddress import IPv4Address from typing import Dict, Optional diff --git a/src/primaite/simulator/system/services/icmp/__init__.py b/src/primaite/simulator/system/services/icmp/__init__.py index 836b79af..be6c00e7 100644 --- a/src/primaite/simulator/system/services/icmp/__init__.py +++ b/src/primaite/simulator/system/services/icmp/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/system/services/icmp/icmp.py b/src/primaite/simulator/system/services/icmp/icmp.py index 933d0591..84ad995d 100644 --- a/src/primaite/simulator/system/services/icmp/icmp.py +++ b/src/primaite/simulator/system/services/icmp/icmp.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK 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 index 63fbd4b2..19c0ac2d 100644 --- a/src/primaite/simulator/system/services/icmp/router_icmp.py +++ b/src/primaite/simulator/system/services/icmp/router_icmp.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK # class RouterICMP(ICMP): # """ # A class to represent a router's Internet Control Message Protocol (ICMP) handler. diff --git a/src/primaite/simulator/system/services/ntp/__init__.py b/src/primaite/simulator/system/services/ntp/__init__.py index 836b79af..be6c00e7 100644 --- a/src/primaite/simulator/system/services/ntp/__init__.py +++ b/src/primaite/simulator/system/services/ntp/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/system/services/ntp/ntp_client.py b/src/primaite/simulator/system/services/ntp/ntp_client.py index 9606c61f..ed89971f 100644 --- a/src/primaite/simulator/system/services/ntp/ntp_client.py +++ b/src/primaite/simulator/system/services/ntp/ntp_client.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK 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 6e73ccc6..b674a296 100644 --- a/src/primaite/simulator/system/services/ntp/ntp_server.py +++ b/src/primaite/simulator/system/services/ntp/ntp_server.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from datetime import datetime from typing import Dict, Optional diff --git a/src/primaite/simulator/system/services/service.py b/src/primaite/simulator/system/services/service.py index 3dc080b4..4f0b879c 100644 --- a/src/primaite/simulator/system/services/service.py +++ b/src/primaite/simulator/system/services/service.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from __future__ import annotations from abc import abstractmethod diff --git a/src/primaite/simulator/system/services/terminal/__init__.py b/src/primaite/simulator/system/services/terminal/__init__.py index 836b79af..be6c00e7 100644 --- a/src/primaite/simulator/system/services/terminal/__init__.py +++ b/src/primaite/simulator/system/services/terminal/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/src/primaite/simulator/system/services/terminal/terminal.py b/src/primaite/simulator/system/services/terminal/terminal.py index e26e77f6..ae3557f7 100644 --- a/src/primaite/simulator/system/services/terminal/terminal.py +++ b/src/primaite/simulator/system/services/terminal/terminal.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from __future__ import annotations from abc import abstractmethod diff --git a/src/primaite/simulator/system/services/web_server/__init__.py b/src/primaite/simulator/system/services/web_server/__init__.py index 836b79af..be6c00e7 100644 --- a/src/primaite/simulator/system/services/web_server/__init__.py +++ b/src/primaite/simulator/system/services/web_server/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK 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 1aab374d..75d9c472 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,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK 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 34c893eb..6fb09a16 100644 --- a/src/primaite/simulator/system/software.py +++ b/src/primaite/simulator/system/software.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import copy from abc import abstractmethod from datetime import datetime diff --git a/src/primaite/utils/__init__.py b/src/primaite/utils/__init__.py index 1dced372..4d7c430e 100644 --- a/src/primaite/utils/__init__.py +++ b/src/primaite/utils/__init__.py @@ -1,2 +1,2 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK """Utilities for PrimAITE.""" diff --git a/src/primaite/utils/cli/__init__.py b/src/primaite/utils/cli/__init__.py index 836b79af..be6c00e7 100644 --- a/src/primaite/utils/cli/__init__.py +++ b/src/primaite/utils/cli/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/src/primaite/utils/cli/dev_cli.py b/src/primaite/utils/cli/dev_cli.py index 581cd0b1..8946a4ca 100644 --- a/src/primaite/utils/cli/dev_cli.py +++ b/src/primaite/utils/cli/dev_cli.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import click import typer from rich import print diff --git a/src/primaite/utils/cli/primaite_config_utils.py b/src/primaite/utils/cli/primaite_config_utils.py index 1fefd0a4..635be5a7 100644 --- a/src/primaite/utils/cli/primaite_config_utils.py +++ b/src/primaite/utils/cli/primaite_config_utils.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import Dict, Optional import yaml diff --git a/src/primaite/utils/converters.py b/src/primaite/utils/converters.py index 95956448..f803851d 100644 --- a/src/primaite/utils/converters.py +++ b/src/primaite/utils/converters.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from enum import Enum from typing import Any, Dict diff --git a/src/primaite/utils/package_data.py b/src/primaite/utils/package_data.py index ed091dd0..af0252f9 100644 --- a/src/primaite/utils/package_data.py +++ b/src/primaite/utils/package_data.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import os from logging import Logger from pathlib import Path diff --git a/src/primaite/utils/session_metadata_parser.py b/src/primaite/utils/session_metadata_parser.py index 1a7345ea..f6594666 100644 --- a/src/primaite/utils/session_metadata_parser.py +++ b/src/primaite/utils/session_metadata_parser.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK # flake8: noqa raise DeprecationWarning( "Benchmarking depends on deprecated functionality and it has not been updated to primaite v3 yet." diff --git a/src/primaite/utils/session_output_reader.py b/src/primaite/utils/session_output_reader.py index f25bbe6a..b9ad68a1 100644 --- a/src/primaite/utils/session_output_reader.py +++ b/src/primaite/utils/session_output_reader.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK # flake8: noqa raise DeprecationWarning( "Benchmarking depends on deprecated functionality and it has not been updated to primaite v3 yet." diff --git a/src/primaite/utils/session_output_writer.py b/src/primaite/utils/session_output_writer.py index a8cefe35..75a97f60 100644 --- a/src/primaite/utils/session_output_writer.py +++ b/src/primaite/utils/session_output_writer.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK # flake8: noqa raise DeprecationWarning( "Benchmarking depends on deprecated functionality and it has not been updated to primaite v3 yet." diff --git a/src/primaite/utils/validation/__init__.py b/src/primaite/utils/validation/__init__.py index 836b79af..be6c00e7 100644 --- a/src/primaite/utils/validation/__init__.py +++ b/src/primaite/utils/validation/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/src/primaite/utils/validation/ip_protocol.py b/src/primaite/utils/validation/ip_protocol.py index 654a5156..4e358305 100644 --- a/src/primaite/utils/validation/ip_protocol.py +++ b/src/primaite/utils/validation/ip_protocol.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK # Define a custom IP protocol validator from typing import Any diff --git a/src/primaite/utils/validation/ipv4_address.py b/src/primaite/utils/validation/ipv4_address.py index c385ed1e..eb0e2574 100644 --- a/src/primaite/utils/validation/ipv4_address.py +++ b/src/primaite/utils/validation/ipv4_address.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address diff --git a/src/primaite/utils/validation/port.py b/src/primaite/utils/validation/port.py index 564e843c..90c36add 100644 --- a/src/primaite/utils/validation/port.py +++ b/src/primaite/utils/validation/port.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK # Define a custom port validator from typing import Any diff --git a/tests/__init__.py b/tests/__init__.py index 900649b2..846ec808 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from pathlib import Path from typing import Final diff --git a/tests/conftest.py b/tests/conftest.py index 0d73aa07..bd1b79ee 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import Any, Dict, Tuple import pytest @@ -471,6 +471,33 @@ def game_and_agent(): action_space = ActionManager( actions=actions, # ALL POSSIBLE ACTIONS + nodes=[ + { + "node_name": "client_1", + "applications": [ + {"application_name": "WebBrowser"}, + {"application_name": "DoSBot"}, + {"application_name": "C2Server"}, + ], + "folders": [{"folder_name": "downloads", "files": [{"file_name": "cat.png"}]}], + }, + { + "node_name": "server_1", + "services": [{"service_name": "DNSServer"}], + "applications": [{"application_name": "C2Beacon"}], + }, + {"node_name": "server_2", "services": [{"service_name": "WebServer"}]}, + {"node_name": "router"}, + ], + max_folders_per_node=2, + max_files_per_folder=2, + max_services_per_node=2, + max_applications_per_node=3, + max_nics_per_node=2, + max_acl_rules=10, + protocols=["TCP", "UDP", "ICMP"], + ports=["HTTP", "DNS", "ARP"], + ip_list=["10.0.1.1", "10.0.1.2", "10.0.2.1", "10.0.2.2", "10.0.2.3"], act_map={}, ) observation_space = ObservationManager(NestedObservation(components={})) diff --git a/tests/e2e_integration_tests/__init__.py b/tests/e2e_integration_tests/__init__.py index 836b79af..be6c00e7 100644 --- a/tests/e2e_integration_tests/__init__.py +++ b/tests/e2e_integration_tests/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/tests/e2e_integration_tests/action_masking/__init__.py b/tests/e2e_integration_tests/action_masking/__init__.py index 836b79af..be6c00e7 100644 --- a/tests/e2e_integration_tests/action_masking/__init__.py +++ b/tests/e2e_integration_tests/action_masking/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/tests/e2e_integration_tests/action_masking/test_agents_use_action_masks.py b/tests/e2e_integration_tests/action_masking/test_agents_use_action_masks.py index a34d430b..addf6dca 100644 --- a/tests/e2e_integration_tests/action_masking/test_agents_use_action_masks.py +++ b/tests/e2e_integration_tests/action_masking/test_agents_use_action_masks.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import Dict import pytest diff --git a/tests/e2e_integration_tests/environments/__init__.py b/tests/e2e_integration_tests/environments/__init__.py index 836b79af..be6c00e7 100644 --- a/tests/e2e_integration_tests/environments/__init__.py +++ b/tests/e2e_integration_tests/environments/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/tests/e2e_integration_tests/environments/test_rllib_multi_agent_environment.py b/tests/e2e_integration_tests/environments/test_rllib_multi_agent_environment.py index 06b080d8..26e690d0 100644 --- a/tests/e2e_integration_tests/environments/test_rllib_multi_agent_environment.py +++ b/tests/e2e_integration_tests/environments/test_rllib_multi_agent_environment.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import yaml from ray.rllib.algorithms.ppo import PPOConfig diff --git a/tests/e2e_integration_tests/environments/test_rllib_single_agent_environment.py b/tests/e2e_integration_tests/environments/test_rllib_single_agent_environment.py index da0ca458..265257e4 100644 --- a/tests/e2e_integration_tests/environments/test_rllib_single_agent_environment.py +++ b/tests/e2e_integration_tests/environments/test_rllib_single_agent_environment.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import tempfile from pathlib import Path diff --git a/tests/e2e_integration_tests/environments/test_sb3_environment.py b/tests/e2e_integration_tests/environments/test_sb3_environment.py index 9ca3525a..a07d5d2e 100644 --- a/tests/e2e_integration_tests/environments/test_sb3_environment.py +++ b/tests/e2e_integration_tests/environments/test_sb3_environment.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK """Test that we can create a primaite environment and train sb3 agent with no crash.""" import tempfile from pathlib import Path diff --git a/tests/e2e_integration_tests/test_environment.py b/tests/e2e_integration_tests/test_environment.py index 881681aa..dcd51193 100644 --- a/tests/e2e_integration_tests/test_environment.py +++ b/tests/e2e_integration_tests/test_environment.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import pydantic import pytest import yaml diff --git a/tests/e2e_integration_tests/test_uc2_data_manipulation_scenario.py b/tests/e2e_integration_tests/test_uc2_data_manipulation_scenario.py index fa4781db..7ec38d72 100644 --- a/tests/e2e_integration_tests/test_uc2_data_manipulation_scenario.py +++ b/tests/e2e_integration_tests/test_uc2_data_manipulation_scenario.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import yaml from primaite.game.game import PrimaiteGame diff --git a/tests/integration_tests/__init__.py b/tests/integration_tests/__init__.py index 836b79af..be6c00e7 100644 --- a/tests/integration_tests/__init__.py +++ b/tests/integration_tests/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/tests/integration_tests/cli/__init__.py b/tests/integration_tests/cli/__init__.py index 603d228f..cfce7ae6 100644 --- a/tests/integration_tests/cli/__init__.py +++ b/tests/integration_tests/cli/__init__.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import List from typer.testing import CliRunner, Result diff --git a/tests/integration_tests/cli/test_dev_cli.py b/tests/integration_tests/cli/test_dev_cli.py index 16c3de9f..cd390555 100644 --- a/tests/integration_tests/cli/test_dev_cli.py +++ b/tests/integration_tests/cli/test_dev_cli.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import os import shutil import tempfile diff --git a/tests/integration_tests/component_creation/__init__.py b/tests/integration_tests/component_creation/__init__.py index 836b79af..be6c00e7 100644 --- a/tests/integration_tests/component_creation/__init__.py +++ b/tests/integration_tests/component_creation/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/tests/integration_tests/component_creation/test_action_integration.py b/tests/integration_tests/component_creation/test_action_integration.py index 8b81b7d3..7bdc80fc 100644 --- a/tests/integration_tests/component_creation/test_action_integration.py +++ b/tests/integration_tests/component_creation/test_action_integration.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from primaite.simulator.core import RequestType from primaite.simulator.network.hardware.nodes.host.computer import Computer from primaite.simulator.network.hardware.nodes.host.server import Server diff --git a/tests/integration_tests/component_creation/test_permission_system.py b/tests/integration_tests/component_creation/test_permission_system.py index c7faa81b..baf75523 100644 --- a/tests/integration_tests/component_creation/test_permission_system.py +++ b/tests/integration_tests/component_creation/test_permission_system.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from enum import Enum from typing import Dict, List, Literal diff --git a/tests/integration_tests/configuration_file_parsing/__init__.py b/tests/integration_tests/configuration_file_parsing/__init__.py index 09861acb..7e23a4c2 100644 --- a/tests/integration_tests/configuration_file_parsing/__init__.py +++ b/tests/integration_tests/configuration_file_parsing/__init__.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from pathlib import Path from typing import Union diff --git a/tests/integration_tests/configuration_file_parsing/nodes/__init__.py b/tests/integration_tests/configuration_file_parsing/nodes/__init__.py index 836b79af..be6c00e7 100644 --- a/tests/integration_tests/configuration_file_parsing/nodes/__init__.py +++ b/tests/integration_tests/configuration_file_parsing/nodes/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/tests/integration_tests/configuration_file_parsing/nodes/network/__init__.py b/tests/integration_tests/configuration_file_parsing/nodes/network/__init__.py index 836b79af..be6c00e7 100644 --- a/tests/integration_tests/configuration_file_parsing/nodes/network/__init__.py +++ b/tests/integration_tests/configuration_file_parsing/nodes/network/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/tests/integration_tests/configuration_file_parsing/nodes/network/test_firewall_config.py b/tests/integration_tests/configuration_file_parsing/nodes/network/test_firewall_config.py index 234e7342..7f251613 100644 --- a/tests/integration_tests/configuration_file_parsing/nodes/network/test_firewall_config.py +++ b/tests/integration_tests/configuration_file_parsing/nodes/network/test_firewall_config.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address import pytest diff --git a/tests/integration_tests/configuration_file_parsing/nodes/network/test_router_config.py b/tests/integration_tests/configuration_file_parsing/nodes/network/test_router_config.py index 16f4dee5..d10c7dbb 100644 --- a/tests/integration_tests/configuration_file_parsing/nodes/network/test_router_config.py +++ b/tests/integration_tests/configuration_file_parsing/nodes/network/test_router_config.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import pytest from primaite.simulator.network.container import Network diff --git a/tests/integration_tests/configuration_file_parsing/nodes/test_node_config.py b/tests/integration_tests/configuration_file_parsing/nodes/test_node_config.py index 764a7aac..8526ab78 100644 --- a/tests/integration_tests/configuration_file_parsing/nodes/test_node_config.py +++ b/tests/integration_tests/configuration_file_parsing/nodes/test_node_config.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from primaite.config.load import data_manipulation_config_path from primaite.simulator.network.container import Network from primaite.simulator.network.hardware.node_operating_state import NodeOperatingState diff --git a/tests/integration_tests/configuration_file_parsing/software_installation_and_configuration.py b/tests/integration_tests/configuration_file_parsing/software_installation_and_configuration.py index 0ff6754d..a642564c 100644 --- a/tests/integration_tests/configuration_file_parsing/software_installation_and_configuration.py +++ b/tests/integration_tests/configuration_file_parsing/software_installation_and_configuration.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address from pathlib import Path from typing import Union diff --git a/tests/integration_tests/configuration_file_parsing/test_episode_scheduler.py b/tests/integration_tests/configuration_file_parsing/test_episode_scheduler.py index c588829b..13be830b 100644 --- a/tests/integration_tests/configuration_file_parsing/test_episode_scheduler.py +++ b/tests/integration_tests/configuration_file_parsing/test_episode_scheduler.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import pytest import yaml diff --git a/tests/integration_tests/configuration_file_parsing/test_game_options_config.py b/tests/integration_tests/configuration_file_parsing/test_game_options_config.py index 4153adc0..32d88c92 100644 --- a/tests/integration_tests/configuration_file_parsing/test_game_options_config.py +++ b/tests/integration_tests/configuration_file_parsing/test_game_options_config.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from pathlib import Path from typing import Union diff --git a/tests/integration_tests/configuration_file_parsing/test_io_settings.py b/tests/integration_tests/configuration_file_parsing/test_io_settings.py index 79812d80..82977b82 100644 --- a/tests/integration_tests/configuration_file_parsing/test_io_settings.py +++ b/tests/integration_tests/configuration_file_parsing/test_io_settings.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from pathlib import Path from typing import Union diff --git a/tests/integration_tests/configuration_file_parsing/test_no_nodes_links_agents_config.py b/tests/integration_tests/configuration_file_parsing/test_no_nodes_links_agents_config.py index 016d264f..26fc562d 100644 --- a/tests/integration_tests/configuration_file_parsing/test_no_nodes_links_agents_config.py +++ b/tests/integration_tests/configuration_file_parsing/test_no_nodes_links_agents_config.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import yaml from primaite.game.game import PrimaiteGame diff --git a/tests/integration_tests/configuration_file_parsing/test_software_fix_duration.py b/tests/integration_tests/configuration_file_parsing/test_software_fix_duration.py index b1c644cc..168ebee0 100644 --- a/tests/integration_tests/configuration_file_parsing/test_software_fix_duration.py +++ b/tests/integration_tests/configuration_file_parsing/test_software_fix_duration.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import copy from pathlib import Path from typing import Union diff --git a/tests/integration_tests/extensions/applications/extended_application.py b/tests/integration_tests/extensions/applications/extended_application.py index 9863dbba..70dc7cba 100644 --- a/tests/integration_tests/extensions/applications/extended_application.py +++ b/tests/integration_tests/extensions/applications/extended_application.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from enum import Enum from ipaddress import IPv4Address from typing import Dict, List, Optional diff --git a/tests/integration_tests/extensions/nodes/giga_switch.py b/tests/integration_tests/extensions/nodes/giga_switch.py index 37a05b6e..e4100741 100644 --- a/tests/integration_tests/extensions/nodes/giga_switch.py +++ b/tests/integration_tests/extensions/nodes/giga_switch.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import Dict from prettytable import MARKDOWN, PrettyTable diff --git a/tests/integration_tests/extensions/nodes/super_computer.py b/tests/integration_tests/extensions/nodes/super_computer.py index 4af1b748..80f7e3c3 100644 --- a/tests/integration_tests/extensions/nodes/super_computer.py +++ b/tests/integration_tests/extensions/nodes/super_computer.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import ClassVar, Dict from primaite.simulator.network.hardware.nodes.host.host_node import HostNode, NIC diff --git a/tests/integration_tests/extensions/services/extended_service.py b/tests/integration_tests/extensions/services/extended_service.py index 0924a91b..ddaf4a1e 100644 --- a/tests/integration_tests/extensions/services/extended_service.py +++ b/tests/integration_tests/extensions/services/extended_service.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address from typing import Any, Dict, List, Literal, Optional, Union from uuid import uuid4 diff --git a/tests/integration_tests/extensions/test_extendable_config.py b/tests/integration_tests/extensions/test_extendable_config.py index 5515d900..5addcbd7 100644 --- a/tests/integration_tests/extensions/test_extendable_config.py +++ b/tests/integration_tests/extensions/test_extendable_config.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import os from primaite.config.load import get_extended_config_path diff --git a/tests/integration_tests/game_layer/actions/__init__.py b/tests/integration_tests/game_layer/actions/__init__.py index 836b79af..be6c00e7 100644 --- a/tests/integration_tests/game_layer/actions/__init__.py +++ b/tests/integration_tests/game_layer/actions/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/tests/integration_tests/game_layer/actions/test_application_request_permission.py b/tests/integration_tests/game_layer/actions/test_application_request_permission.py index e90fa591..36a7ae57 100644 --- a/tests/integration_tests/game_layer/actions/test_application_request_permission.py +++ b/tests/integration_tests/game_layer/actions/test_application_request_permission.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import Tuple import pytest diff --git a/tests/integration_tests/game_layer/actions/test_c2_suite_actions.py b/tests/integration_tests/game_layer/actions/test_c2_suite_actions.py index 36fee9a0..d73c9834 100644 --- a/tests/integration_tests/game_layer/actions/test_c2_suite_actions.py +++ b/tests/integration_tests/game_layer/actions/test_c2_suite_actions.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address from typing import Tuple diff --git a/tests/integration_tests/game_layer/actions/test_configure_actions.py b/tests/integration_tests/game_layer/actions/test_configure_actions.py index 8c97573a..7bf45fb4 100644 --- a/tests/integration_tests/game_layer/actions/test_configure_actions.py +++ b/tests/integration_tests/game_layer/actions/test_configure_actions.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address import pytest diff --git a/tests/integration_tests/game_layer/actions/test_file_request_permission.py b/tests/integration_tests/game_layer/actions/test_file_request_permission.py index 91aa9fcd..1c143aed 100644 --- a/tests/integration_tests/game_layer/actions/test_file_request_permission.py +++ b/tests/integration_tests/game_layer/actions/test_file_request_permission.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import uuid from typing import Tuple diff --git a/tests/integration_tests/game_layer/actions/test_folder_request_permission.py b/tests/integration_tests/game_layer/actions/test_folder_request_permission.py index 56bbbd4e..e5e0806a 100644 --- a/tests/integration_tests/game_layer/actions/test_folder_request_permission.py +++ b/tests/integration_tests/game_layer/actions/test_folder_request_permission.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import uuid from typing import Tuple diff --git a/tests/integration_tests/game_layer/actions/test_nic_request_permission.py b/tests/integration_tests/game_layer/actions/test_nic_request_permission.py index 8846809d..d796b75e 100644 --- a/tests/integration_tests/game_layer/actions/test_nic_request_permission.py +++ b/tests/integration_tests/game_layer/actions/test_nic_request_permission.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import Tuple import pytest diff --git a/tests/integration_tests/game_layer/actions/test_node_request_permission.py b/tests/integration_tests/game_layer/actions/test_node_request_permission.py index 8fbbbd70..c34103bc 100644 --- a/tests/integration_tests/game_layer/actions/test_node_request_permission.py +++ b/tests/integration_tests/game_layer/actions/test_node_request_permission.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import Tuple import pytest diff --git a/tests/integration_tests/game_layer/actions/test_service_request_permission.py b/tests/integration_tests/game_layer/actions/test_service_request_permission.py index ebc9fd3b..3054c73b 100644 --- a/tests/integration_tests/game_layer/actions/test_service_request_permission.py +++ b/tests/integration_tests/game_layer/actions/test_service_request_permission.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import Tuple import pytest diff --git a/tests/integration_tests/game_layer/actions/test_terminal_actions.py b/tests/integration_tests/game_layer/actions/test_terminal_actions.py index 96110656..a70cea72 100644 --- a/tests/integration_tests/game_layer/actions/test_terminal_actions.py +++ b/tests/integration_tests/game_layer/actions/test_terminal_actions.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import Tuple import pytest diff --git a/tests/integration_tests/game_layer/observations/__init__.py b/tests/integration_tests/game_layer/observations/__init__.py index 836b79af..be6c00e7 100644 --- a/tests/integration_tests/game_layer/observations/__init__.py +++ b/tests/integration_tests/game_layer/observations/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/tests/integration_tests/game_layer/observations/test_acl_observations.py b/tests/integration_tests/game_layer/observations/test_acl_observations.py index 02cf005a..e7212f3c 100644 --- a/tests/integration_tests/game_layer/observations/test_acl_observations.py +++ b/tests/integration_tests/game_layer/observations/test_acl_observations.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import pytest from primaite.game.agent.observations.acl_observation import ACLObservation diff --git a/tests/integration_tests/game_layer/observations/test_file_system_observations.py b/tests/integration_tests/game_layer/observations/test_file_system_observations.py index 0268cb95..e2ab2990 100644 --- a/tests/integration_tests/game_layer/observations/test_file_system_observations.py +++ b/tests/integration_tests/game_layer/observations/test_file_system_observations.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import pytest from gymnasium import spaces diff --git a/tests/integration_tests/game_layer/observations/test_firewall_observation.py b/tests/integration_tests/game_layer/observations/test_firewall_observation.py index 97608132..05cf910c 100644 --- a/tests/integration_tests/game_layer/observations/test_firewall_observation.py +++ b/tests/integration_tests/game_layer/observations/test_firewall_observation.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from primaite.game.agent.observations.firewall_observation import FirewallObservation from primaite.simulator.network.container import Network from primaite.simulator.network.hardware.node_operating_state import NodeOperatingState diff --git a/tests/integration_tests/game_layer/observations/test_link_observations.py b/tests/integration_tests/game_layer/observations/test_link_observations.py index 630e29ea..7d1c1939 100644 --- a/tests/integration_tests/game_layer/observations/test_link_observations.py +++ b/tests/integration_tests/game_layer/observations/test_link_observations.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import pytest from gymnasium import spaces diff --git a/tests/integration_tests/game_layer/observations/test_nic_observations.py b/tests/integration_tests/game_layer/observations/test_nic_observations.py index 0ad03198..8254dad2 100644 --- a/tests/integration_tests/game_layer/observations/test_nic_observations.py +++ b/tests/integration_tests/game_layer/observations/test_nic_observations.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from pathlib import Path from typing import Union diff --git a/tests/integration_tests/game_layer/observations/test_node_observations.py b/tests/integration_tests/game_layer/observations/test_node_observations.py index 63ca8f6b..69d9f106 100644 --- a/tests/integration_tests/game_layer/observations/test_node_observations.py +++ b/tests/integration_tests/game_layer/observations/test_node_observations.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import copy from uuid import uuid4 diff --git a/tests/integration_tests/game_layer/observations/test_router_observation.py b/tests/integration_tests/game_layer/observations/test_router_observation.py index f4bfb193..4ced02f5 100644 --- a/tests/integration_tests/game_layer/observations/test_router_observation.py +++ b/tests/integration_tests/game_layer/observations/test_router_observation.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from pprint import pprint from primaite.game.agent.observations.acl_observation import ACLObservation diff --git a/tests/integration_tests/game_layer/observations/test_software_observations.py b/tests/integration_tests/game_layer/observations/test_software_observations.py index 291ee395..998aa755 100644 --- a/tests/integration_tests/game_layer/observations/test_software_observations.py +++ b/tests/integration_tests/game_layer/observations/test_software_observations.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import pytest from gymnasium import spaces diff --git a/tests/integration_tests/game_layer/observations/test_user_observations.py b/tests/integration_tests/game_layer/observations/test_user_observations.py index 92c533c9..e7287eee 100644 --- a/tests/integration_tests/game_layer/observations/test_user_observations.py +++ b/tests/integration_tests/game_layer/observations/test_user_observations.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import pytest from primaite.session.environment import PrimaiteGymEnv diff --git a/tests/integration_tests/game_layer/test_RNG_seed.py b/tests/integration_tests/game_layer/test_RNG_seed.py index 464f95db..e772af32 100644 --- a/tests/integration_tests/game_layer/test_RNG_seed.py +++ b/tests/integration_tests/game_layer/test_RNG_seed.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from pprint import pprint import pytest diff --git a/tests/integration_tests/game_layer/test_action_mask.py b/tests/integration_tests/game_layer/test_action_mask.py index 22c00aa4..7a1475c2 100644 --- a/tests/integration_tests/game_layer/test_action_mask.py +++ b/tests/integration_tests/game_layer/test_action_mask.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from primaite.session.environment import PrimaiteGymEnv from primaite.simulator.network.hardware.node_operating_state import NodeOperatingState from primaite.simulator.network.hardware.nodes.host.host_node import HostNode diff --git a/tests/integration_tests/game_layer/test_actions.py b/tests/integration_tests/game_layer/test_actions.py index ff86dbf0..859c056c 100644 --- a/tests/integration_tests/game_layer/test_actions.py +++ b/tests/integration_tests/game_layer/test_actions.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK # Plan for creating integration tests for the actions: # I need to test that the requests coming out of the actions have the intended effect on the simulation. # I can do this by creating a simulation, and then running the action on the simulation, and then checking diff --git a/tests/integration_tests/game_layer/test_observations.py b/tests/integration_tests/game_layer/test_observations.py index 23364f13..d5679007 100644 --- a/tests/integration_tests/game_layer/test_observations.py +++ b/tests/integration_tests/game_layer/test_observations.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from gymnasium import spaces from primaite.game.agent.observations.file_system_observations import FileObservation diff --git a/tests/integration_tests/game_layer/test_rewards.py b/tests/integration_tests/game_layer/test_rewards.py index a2453782..882c0923 100644 --- a/tests/integration_tests/game_layer/test_rewards.py +++ b/tests/integration_tests/game_layer/test_rewards.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import pytest import yaml diff --git a/tests/integration_tests/network/__init__.py b/tests/integration_tests/network/__init__.py index 836b79af..be6c00e7 100644 --- a/tests/integration_tests/network/__init__.py +++ b/tests/integration_tests/network/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/tests/integration_tests/network/test_airspace_config.py b/tests/integration_tests/network/test_airspace_config.py index e8abc0f2..e000f6ae 100644 --- a/tests/integration_tests/network/test_airspace_config.py +++ b/tests/integration_tests/network/test_airspace_config.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import yaml from primaite.game.game import PrimaiteGame diff --git a/tests/integration_tests/network/test_bandwidth_load_checks_before_transmission.py b/tests/integration_tests/network/test_bandwidth_load_checks_before_transmission.py index 36c77fe1..b7317c3d 100644 --- a/tests/integration_tests/network/test_bandwidth_load_checks_before_transmission.py +++ b/tests/integration_tests/network/test_bandwidth_load_checks_before_transmission.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from primaite.simulator.file_system.file_type import FileType from primaite.simulator.network.hardware.nodes.network.router import ACLAction from primaite.simulator.system.services.ftp.ftp_client import FTPClient diff --git a/tests/integration_tests/network/test_broadcast.py b/tests/integration_tests/network/test_broadcast.py index 33fe70c3..f07f02e7 100644 --- a/tests/integration_tests/network/test_broadcast.py +++ b/tests/integration_tests/network/test_broadcast.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address, IPv4Network from typing import Any, Dict, List, Tuple diff --git a/tests/integration_tests/network/test_capture_nmne.py b/tests/integration_tests/network/test_capture_nmne.py index b32d9657..debf5b1c 100644 --- a/tests/integration_tests/network/test_capture_nmne.py +++ b/tests/integration_tests/network/test_capture_nmne.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from primaite.game.agent.observations.nic_observations import NICObservation from primaite.simulator.network.container import Network from primaite.simulator.network.hardware.nodes.host.host_node import NIC diff --git a/tests/integration_tests/network/test_firewall.py b/tests/integration_tests/network/test_firewall.py index 24fbfd05..79452318 100644 --- a/tests/integration_tests/network/test_firewall.py +++ b/tests/integration_tests/network/test_firewall.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address import pytest diff --git a/tests/integration_tests/network/test_frame_transmission.py b/tests/integration_tests/network/test_frame_transmission.py index 327c87e5..fc2d146e 100644 --- a/tests/integration_tests/network/test_frame_transmission.py +++ b/tests/integration_tests/network/test_frame_transmission.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from primaite.simulator.network.container import Network from primaite.simulator.network.hardware.nodes.host.computer import Computer from primaite.simulator.network.hardware.nodes.host.host_node import NIC diff --git a/tests/integration_tests/network/test_multi_lan_internet_example_network.py b/tests/integration_tests/network/test_multi_lan_internet_example_network.py index ea7e1c45..bcc9ad94 100644 --- a/tests/integration_tests/network/test_multi_lan_internet_example_network.py +++ b/tests/integration_tests/network/test_multi_lan_internet_example_network.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from primaite.simulator.network.hardware.nodes.host.computer import Computer from primaite.simulator.network.hardware.nodes.host.server import Server from primaite.simulator.network.networks import multi_lan_internet_network_example diff --git a/tests/integration_tests/network/test_network_creation.py b/tests/integration_tests/network/test_network_creation.py index 1ee3ccc2..794ddde5 100644 --- a/tests/integration_tests/network/test_network_creation.py +++ b/tests/integration_tests/network/test_network_creation.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from primaite.simulator.network.container import Network from primaite.simulator.network.hardware.nodes.host.computer import Computer from primaite.simulator.network.hardware.nodes.host.host_node import NIC diff --git a/tests/integration_tests/network/test_nic_link_connection.py b/tests/integration_tests/network/test_nic_link_connection.py index 8c45f511..ab9160c8 100644 --- a/tests/integration_tests/network/test_nic_link_connection.py +++ b/tests/integration_tests/network/test_nic_link_connection.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import pytest from primaite.simulator.network.hardware.base import Link diff --git a/tests/integration_tests/network/test_routing.py b/tests/integration_tests/network/test_routing.py index 948b409f..04cdbe78 100644 --- a/tests/integration_tests/network/test_routing.py +++ b/tests/integration_tests/network/test_routing.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import Tuple import pytest diff --git a/tests/integration_tests/network/test_switched_network.py b/tests/integration_tests/network/test_switched_network.py index 67392da3..ae0aa8a7 100644 --- a/tests/integration_tests/network/test_switched_network.py +++ b/tests/integration_tests/network/test_switched_network.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK def test_switched_network(client_switch_server): """Tests a node can ping another node via the switch.""" computer, switch, server = client_switch_server diff --git a/tests/integration_tests/network/test_users_creation_from_config.py b/tests/integration_tests/network/test_users_creation_from_config.py index 1963b1dd..8cd3b037 100644 --- a/tests/integration_tests/network/test_users_creation_from_config.py +++ b/tests/integration_tests/network/test_users_creation_from_config.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import yaml from primaite.game.game import PrimaiteGame diff --git a/tests/integration_tests/network/test_wireless_router.py b/tests/integration_tests/network/test_wireless_router.py index 26e50f4a..fb0035e9 100644 --- a/tests/integration_tests/network/test_wireless_router.py +++ b/tests/integration_tests/network/test_wireless_router.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import pytest import yaml diff --git a/tests/integration_tests/system/__init__.py b/tests/integration_tests/system/__init__.py index 836b79af..be6c00e7 100644 --- a/tests/integration_tests/system/__init__.py +++ b/tests/integration_tests/system/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/tests/integration_tests/system/red_applications/test_c2_suite_integration.py b/tests/integration_tests/system/red_applications/test_c2_suite_integration.py index d88f8249..2cbd4d11 100644 --- a/tests/integration_tests/system/red_applications/test_c2_suite_integration.py +++ b/tests/integration_tests/system/red_applications/test_c2_suite_integration.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address from typing import Tuple diff --git a/tests/integration_tests/system/red_applications/test_data_manipulation_bot_and_server.py b/tests/integration_tests/system/red_applications/test_data_manipulation_bot_and_server.py index 3ef6469e..50b0ceac 100644 --- a/tests/integration_tests/system/red_applications/test_data_manipulation_bot_and_server.py +++ b/tests/integration_tests/system/red_applications/test_data_manipulation_bot_and_server.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address from typing import Tuple diff --git a/tests/integration_tests/system/red_applications/test_dos_bot_and_server.py b/tests/integration_tests/system/red_applications/test_dos_bot_and_server.py index cb0195f0..1a09e875 100644 --- a/tests/integration_tests/system/red_applications/test_dos_bot_and_server.py +++ b/tests/integration_tests/system/red_applications/test_dos_bot_and_server.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address from typing import Tuple diff --git a/tests/integration_tests/system/red_applications/test_ransomware_script.py b/tests/integration_tests/system/red_applications/test_ransomware_script.py index 14b83e6a..a5adbb04 100644 --- a/tests/integration_tests/system/red_applications/test_ransomware_script.py +++ b/tests/integration_tests/system/red_applications/test_ransomware_script.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address from typing import Tuple diff --git a/tests/integration_tests/system/test_application_on_node.py b/tests/integration_tests/system/test_application_on_node.py index fc7aa69c..ffb5cc7f 100644 --- a/tests/integration_tests/system/test_application_on_node.py +++ b/tests/integration_tests/system/test_application_on_node.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import Tuple import pytest diff --git a/tests/integration_tests/system/test_arp.py b/tests/integration_tests/system/test_arp.py index 055d58c6..be8656aa 100644 --- a/tests/integration_tests/system/test_arp.py +++ b/tests/integration_tests/system/test_arp.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from primaite.simulator.network.hardware.nodes.network.router import RouterARP from primaite.simulator.system.services.arp.arp import ARP from tests.integration_tests.network.test_routing import multi_hop_network diff --git a/tests/integration_tests/system/test_database_on_node.py b/tests/integration_tests/system/test_database_on_node.py index 674603fa..965b4ae8 100644 --- a/tests/integration_tests/system/test_database_on_node.py +++ b/tests/integration_tests/system/test_database_on_node.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address from typing import Tuple diff --git a/tests/integration_tests/system/test_dns_client_server.py b/tests/integration_tests/system/test_dns_client_server.py index 38caf1a2..480a90bc 100644 --- a/tests/integration_tests/system/test_dns_client_server.py +++ b/tests/integration_tests/system/test_dns_client_server.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address from typing import Tuple diff --git a/tests/integration_tests/system/test_ftp_client_server.py b/tests/integration_tests/system/test_ftp_client_server.py index fa4df0a9..22c5d484 100644 --- a/tests/integration_tests/system/test_ftp_client_server.py +++ b/tests/integration_tests/system/test_ftp_client_server.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import Tuple import pytest diff --git a/tests/integration_tests/system/test_nmap.py b/tests/integration_tests/system/test_nmap.py index d1925a94..c52b5caa 100644 --- a/tests/integration_tests/system/test_nmap.py +++ b/tests/integration_tests/system/test_nmap.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from enum import Enum from ipaddress import IPv4Address, IPv4Network diff --git a/tests/integration_tests/system/test_ntp_client_server.py b/tests/integration_tests/system/test_ntp_client_server.py index 42340eb3..957c1aeb 100644 --- a/tests/integration_tests/system/test_ntp_client_server.py +++ b/tests/integration_tests/system/test_ntp_client_server.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address from time import sleep from typing import Tuple diff --git a/tests/integration_tests/system/test_service_listening_on_ports.py b/tests/integration_tests/system/test_service_listening_on_ports.py index 2d3679ed..7a085ee1 100644 --- a/tests/integration_tests/system/test_service_listening_on_ports.py +++ b/tests/integration_tests/system/test_service_listening_on_ports.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import Any, Dict, List, Set import yaml diff --git a/tests/integration_tests/system/test_service_on_node.py b/tests/integration_tests/system/test_service_on_node.py index 4e73a050..cf9728ce 100644 --- a/tests/integration_tests/system/test_service_on_node.py +++ b/tests/integration_tests/system/test_service_on_node.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import Tuple import pytest diff --git a/tests/integration_tests/system/test_user_session_manager_logins.py b/tests/integration_tests/system/test_user_session_manager_logins.py index 0c591a4b..4318530c 100644 --- a/tests/integration_tests/system/test_user_session_manager_logins.py +++ b/tests/integration_tests/system/test_user_session_manager_logins.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import Tuple from uuid import uuid4 diff --git a/tests/integration_tests/system/test_web_client_server.py b/tests/integration_tests/system/test_web_client_server.py index c1028e8e..05cbae4f 100644 --- a/tests/integration_tests/system/test_web_client_server.py +++ b/tests/integration_tests/system/test_web_client_server.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import Tuple import pytest diff --git a/tests/integration_tests/system/test_web_client_server_and_database.py b/tests/integration_tests/system/test_web_client_server_and_database.py index 8fb6dc18..f2ac1183 100644 --- a/tests/integration_tests/system/test_web_client_server_and_database.py +++ b/tests/integration_tests/system/test_web_client_server_and_database.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address from typing import Tuple diff --git a/tests/integration_tests/test_simulation/__init__.py b/tests/integration_tests/test_simulation/__init__.py index 836b79af..be6c00e7 100644 --- a/tests/integration_tests/test_simulation/__init__.py +++ b/tests/integration_tests/test_simulation/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/tests/integration_tests/test_simulation/test_request_response.py b/tests/integration_tests/test_simulation/test_request_response.py index 21152199..a767f365 100644 --- a/tests/integration_tests/test_simulation/test_request_response.py +++ b/tests/integration_tests/test_simulation/test_request_response.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK # some test cases: # 0. test that sending a request to a valid target results in a success # 1. test that sending a request to a component that doesn't exist results in a failure diff --git a/tests/mock_and_patch/__init__.py b/tests/mock_and_patch/__init__.py index 836b79af..be6c00e7 100644 --- a/tests/mock_and_patch/__init__.py +++ b/tests/mock_and_patch/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/tests/mock_and_patch/get_session_path_mock.py b/tests/mock_and_patch/get_session_path_mock.py index 073028a7..f315fca4 100644 --- a/tests/mock_and_patch/get_session_path_mock.py +++ b/tests/mock_and_patch/get_session_path_mock.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import tempfile from datetime import datetime from pathlib import Path diff --git a/tests/unit_tests/__init__.py b/tests/unit_tests/__init__.py index 836b79af..be6c00e7 100644 --- a/tests/unit_tests/__init__.py +++ b/tests/unit_tests/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/tests/unit_tests/_primaite/__init__.py b/tests/unit_tests/_primaite/__init__.py index 836b79af..be6c00e7 100644 --- a/tests/unit_tests/_primaite/__init__.py +++ b/tests/unit_tests/_primaite/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/tests/unit_tests/_primaite/_game/__init__.py b/tests/unit_tests/_primaite/_game/__init__.py index 836b79af..be6c00e7 100644 --- a/tests/unit_tests/_primaite/_game/__init__.py +++ b/tests/unit_tests/_primaite/_game/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/tests/unit_tests/_primaite/_game/_agent/__init__.py b/tests/unit_tests/_primaite/_game/_agent/__init__.py index 836b79af..be6c00e7 100644 --- a/tests/unit_tests/_primaite/_game/_agent/__init__.py +++ b/tests/unit_tests/_primaite/_game/_agent/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/tests/unit_tests/_primaite/_game/_agent/test_actions.py b/tests/unit_tests/_primaite/_game/_agent/test_actions.py index 9021b8af..46963015 100644 --- a/tests/unit_tests/_primaite/_game/_agent/test_actions.py +++ b/tests/unit_tests/_primaite/_game/_agent/test_actions.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from unittest.mock import Mock import pytest diff --git a/tests/unit_tests/_primaite/_game/_agent/test_agent_log.py b/tests/unit_tests/_primaite/_game/_agent/test_agent_log.py index a7713437..d61e1a23 100644 --- a/tests/unit_tests/_primaite/_game/_agent/test_agent_log.py +++ b/tests/unit_tests/_primaite/_game/_agent/test_agent_log.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from uuid import uuid4 import pytest diff --git a/tests/unit_tests/_primaite/_game/_agent/test_observations.py b/tests/unit_tests/_primaite/_game/_agent/test_observations.py index bb3ad33c..7f590685 100644 --- a/tests/unit_tests/_primaite/_game/_agent/test_observations.py +++ b/tests/unit_tests/_primaite/_game/_agent/test_observations.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import List import pytest diff --git a/tests/unit_tests/_primaite/_game/_agent/test_probabilistic_agent.py b/tests/unit_tests/_primaite/_game/_agent/test_probabilistic_agent.py index 8c06aeed..2fd2da0c 100644 --- a/tests/unit_tests/_primaite/_game/_agent/test_probabilistic_agent.py +++ b/tests/unit_tests/_primaite/_game/_agent/test_probabilistic_agent.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from primaite.game.agent.actions import ActionManager from primaite.game.agent.observations.observation_manager import NestedObservation, ObservationManager from primaite.game.agent.rewards import RewardFunction diff --git a/tests/unit_tests/_primaite/_game/_agent/test_sticky_rewards.py b/tests/unit_tests/_primaite/_game/_agent/test_sticky_rewards.py index 67c4290d..78113f5f 100644 --- a/tests/unit_tests/_primaite/_game/_agent/test_sticky_rewards.py +++ b/tests/unit_tests/_primaite/_game/_agent/test_sticky_rewards.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from primaite.game.agent.interface import AgentHistoryItem from primaite.game.agent.rewards import ( diff --git a/tests/unit_tests/_primaite/_interface/__init__.py b/tests/unit_tests/_primaite/_interface/__init__.py index 836b79af..be6c00e7 100644 --- a/tests/unit_tests/_primaite/_interface/__init__.py +++ b/tests/unit_tests/_primaite/_interface/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/tests/unit_tests/_primaite/_interface/test_request.py b/tests/unit_tests/_primaite/_interface/test_request.py index d9fae083..6067f9e4 100644 --- a/tests/unit_tests/_primaite/_interface/test_request.py +++ b/tests/unit_tests/_primaite/_interface/test_request.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import pytest from pydantic import ValidationError diff --git a/tests/unit_tests/_primaite/_session/__init__.py b/tests/unit_tests/_primaite/_session/__init__.py index 836b79af..be6c00e7 100644 --- a/tests/unit_tests/_primaite/_session/__init__.py +++ b/tests/unit_tests/_primaite/_session/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/tests/unit_tests/_primaite/_session/test_episode_schedule.py b/tests/unit_tests/_primaite/_session/test_episode_schedule.py index ff26bb02..21448339 100644 --- a/tests/unit_tests/_primaite/_session/test_episode_schedule.py +++ b/tests/unit_tests/_primaite/_session/test_episode_schedule.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import pytest import yaml diff --git a/tests/unit_tests/_primaite/_simulator/__init__.py b/tests/unit_tests/_primaite/_simulator/__init__.py index 836b79af..be6c00e7 100644 --- a/tests/unit_tests/_primaite/_simulator/__init__.py +++ b/tests/unit_tests/_primaite/_simulator/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/tests/unit_tests/_primaite/_simulator/_domain/__init__.py b/tests/unit_tests/_primaite/_simulator/_domain/__init__.py index 836b79af..be6c00e7 100644 --- a/tests/unit_tests/_primaite/_simulator/_domain/__init__.py +++ b/tests/unit_tests/_primaite/_simulator/_domain/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/tests/unit_tests/_primaite/_simulator/_domain/test_account.py b/tests/unit_tests/_primaite/_simulator/_domain/test_account.py index f5294844..8db68565 100644 --- a/tests/unit_tests/_primaite/_simulator/_domain/test_account.py +++ b/tests/unit_tests/_primaite/_simulator/_domain/test_account.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK """Test the account module of the simulator.""" import pytest diff --git a/tests/unit_tests/_primaite/_simulator/_domain/test_controller.py b/tests/unit_tests/_primaite/_simulator/_domain/test_controller.py index 836b79af..be6c00e7 100644 --- a/tests/unit_tests/_primaite/_simulator/_domain/test_controller.py +++ b/tests/unit_tests/_primaite/_simulator/_domain/test_controller.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/tests/unit_tests/_primaite/_simulator/_file_system/__init__.py b/tests/unit_tests/_primaite/_simulator/_file_system/__init__.py index 836b79af..be6c00e7 100644 --- a/tests/unit_tests/_primaite/_simulator/_file_system/__init__.py +++ b/tests/unit_tests/_primaite/_simulator/_file_system/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/tests/unit_tests/_primaite/_simulator/_file_system/test_file.py b/tests/unit_tests/_primaite/_simulator/_file_system/test_file.py index 6cbf93c8..0b9bdc8e 100644 --- a/tests/unit_tests/_primaite/_simulator/_file_system/test_file.py +++ b/tests/unit_tests/_primaite/_simulator/_file_system/test_file.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import warnings import pytest diff --git a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_actions.py b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_actions.py index 4ec1ec57..594c7afe 100644 --- a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_actions.py +++ b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_actions.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import Tuple import pytest diff --git a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system.py b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system.py index 5554b9ef..4eb0dd10 100644 --- a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system.py +++ b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import pytest from primaite.simulator.file_system.file import File diff --git a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_actions.py b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_actions.py index 44a4e22a..7d022ea4 100644 --- a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_actions.py +++ b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_actions.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import Tuple import pytest diff --git a/tests/unit_tests/_primaite/_simulator/_file_system/test_folder.py b/tests/unit_tests/_primaite/_simulator/_file_system/test_folder.py index 473e0db2..724d7903 100644 --- a/tests/unit_tests/_primaite/_simulator/_file_system/test_folder.py +++ b/tests/unit_tests/_primaite/_simulator/_file_system/test_folder.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import pytest from primaite.simulator.file_system.file import File diff --git a/tests/unit_tests/_primaite/_simulator/_file_system/test_folder_actions.py b/tests/unit_tests/_primaite/_simulator/_file_system/test_folder_actions.py index 609e29c4..4a561b97 100644 --- a/tests/unit_tests/_primaite/_simulator/_file_system/test_folder_actions.py +++ b/tests/unit_tests/_primaite/_simulator/_file_system/test_folder_actions.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import warnings from typing import Tuple diff --git a/tests/unit_tests/_primaite/_simulator/_network/__init__.py b/tests/unit_tests/_primaite/_simulator/_network/__init__.py index 836b79af..be6c00e7 100644 --- a/tests/unit_tests/_primaite/_simulator/_network/__init__.py +++ b/tests/unit_tests/_primaite/_simulator/_network/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/tests/unit_tests/_primaite/_simulator/_network/_hardware/__init__.py b/tests/unit_tests/_primaite/_simulator/_network/_hardware/__init__.py index 836b79af..be6c00e7 100644 --- a/tests/unit_tests/_primaite/_simulator/_network/_hardware/__init__.py +++ b/tests/unit_tests/_primaite/_simulator/_network/_hardware/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/tests/unit_tests/_primaite/_simulator/_network/_hardware/nodes/__init__.py b/tests/unit_tests/_primaite/_simulator/_network/_hardware/nodes/__init__.py index 836b79af..be6c00e7 100644 --- a/tests/unit_tests/_primaite/_simulator/_network/_hardware/nodes/__init__.py +++ b/tests/unit_tests/_primaite/_simulator/_network/_hardware/nodes/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/tests/unit_tests/_primaite/_simulator/_network/_hardware/nodes/test_acl.py b/tests/unit_tests/_primaite/_simulator/_network/_hardware/nodes/test_acl.py index 79392d66..6eca0c44 100644 --- a/tests/unit_tests/_primaite/_simulator/_network/_hardware/nodes/test_acl.py +++ b/tests/unit_tests/_primaite/_simulator/_network/_hardware/nodes/test_acl.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address import pytest diff --git a/tests/unit_tests/_primaite/_simulator/_network/_hardware/nodes/test_router.py b/tests/unit_tests/_primaite/_simulator/_network/_hardware/nodes/test_router.py index fe0c3a57..fe9387de 100644 --- a/tests/unit_tests/_primaite/_simulator/_network/_hardware/nodes/test_router.py +++ b/tests/unit_tests/_primaite/_simulator/_network/_hardware/nodes/test_router.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address from primaite.simulator.network.hardware.nodes.network.router import ACLAction, Router diff --git a/tests/unit_tests/_primaite/_simulator/_network/_hardware/nodes/test_switch.py b/tests/unit_tests/_primaite/_simulator/_network/_hardware/nodes/test_switch.py index e6bff60e..2613d536 100644 --- a/tests/unit_tests/_primaite/_simulator/_network/_hardware/nodes/test_switch.py +++ b/tests/unit_tests/_primaite/_simulator/_network/_hardware/nodes/test_switch.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import pytest from primaite.simulator.network.hardware.node_operating_state import NodeOperatingState diff --git a/tests/unit_tests/_primaite/_simulator/_network/_hardware/test_network_interface_actions.py b/tests/unit_tests/_primaite/_simulator/_network/_hardware/test_network_interface_actions.py index 5cff4407..f35cf171 100644 --- a/tests/unit_tests/_primaite/_simulator/_network/_hardware/test_network_interface_actions.py +++ b/tests/unit_tests/_primaite/_simulator/_network/_hardware/test_network_interface_actions.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import pytest from primaite.simulator.network.hardware.base import NetworkInterface, Node diff --git a/tests/unit_tests/_primaite/_simulator/_network/_hardware/test_nic.py b/tests/unit_tests/_primaite/_simulator/_network/_hardware/test_nic.py index f9ff0328..29d5ec67 100644 --- a/tests/unit_tests/_primaite/_simulator/_network/_hardware/test_nic.py +++ b/tests/unit_tests/_primaite/_simulator/_network/_hardware/test_nic.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import re from ipaddress import IPv4Address 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 605f8c3b..44c5c781 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 @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import pytest from primaite.simulator.file_system.file import File diff --git a/tests/unit_tests/_primaite/_simulator/_network/_transmission/__init__.py b/tests/unit_tests/_primaite/_simulator/_network/_transmission/__init__.py index 836b79af..be6c00e7 100644 --- a/tests/unit_tests/_primaite/_simulator/_network/_transmission/__init__.py +++ b/tests/unit_tests/_primaite/_simulator/_network/_transmission/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/tests/unit_tests/_primaite/_simulator/_network/_transmission/test_data_link_layer.py b/tests/unit_tests/_primaite/_simulator/_network/_transmission/test_data_link_layer.py index 161d9cb4..e7e425b1 100644 --- a/tests/unit_tests/_primaite/_simulator/_network/_transmission/test_data_link_layer.py +++ b/tests/unit_tests/_primaite/_simulator/_network/_transmission/test_data_link_layer.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import pytest from primaite.simulator.network.protocols.icmp import ICMPPacket diff --git a/tests/unit_tests/_primaite/_simulator/_network/_transmission/test_network_layer.py b/tests/unit_tests/_primaite/_simulator/_network/_transmission/test_network_layer.py index 990a0bbf..658726b5 100644 --- a/tests/unit_tests/_primaite/_simulator/_network/_transmission/test_network_layer.py +++ b/tests/unit_tests/_primaite/_simulator/_network/_transmission/test_network_layer.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import pytest from primaite.simulator.network.protocols.icmp import ICMPPacket, ICMPType diff --git a/tests/unit_tests/_primaite/_simulator/_network/test_container.py b/tests/unit_tests/_primaite/_simulator/_network/test_container.py index b1de710a..f764f9b5 100644 --- a/tests/unit_tests/_primaite/_simulator/_network/test_container.py +++ b/tests/unit_tests/_primaite/_simulator/_network/test_container.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import json import pytest diff --git a/tests/unit_tests/_primaite/_simulator/_network/test_creation.py b/tests/unit_tests/_primaite/_simulator/_network/test_creation.py index 9885df67..2e86ebbc 100644 --- a/tests/unit_tests/_primaite/_simulator/_network/test_creation.py +++ b/tests/unit_tests/_primaite/_simulator/_network/test_creation.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import pytest from primaite.simulator.network.container import Network diff --git a/tests/unit_tests/_primaite/_simulator/_network/test_utils.py b/tests/unit_tests/_primaite/_simulator/_network/test_utils.py index d86aa876..c80189c1 100644 --- a/tests/unit_tests/_primaite/_simulator/_network/test_utils.py +++ b/tests/unit_tests/_primaite/_simulator/_network/test_utils.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from primaite.simulator.network.utils import convert_bytes_to_megabits, convert_megabits_to_bytes diff --git a/tests/unit_tests/_primaite/_simulator/_system/__init__.py b/tests/unit_tests/_primaite/_simulator/_system/__init__.py index 836b79af..be6c00e7 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/__init__.py +++ b/tests/unit_tests/_primaite/_simulator/_system/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/tests/unit_tests/_primaite/_simulator/_system/_applications/__init__.py b/tests/unit_tests/_primaite/_simulator/_system/_applications/__init__.py index 836b79af..be6c00e7 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/_applications/__init__.py +++ b/tests/unit_tests/_primaite/_simulator/_system/_applications/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/tests/unit_tests/_primaite/_simulator/_system/_applications/_red_applications/__init__.py b/tests/unit_tests/_primaite/_simulator/_system/_applications/_red_applications/__init__.py index 836b79af..be6c00e7 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/_applications/_red_applications/__init__.py +++ b/tests/unit_tests/_primaite/_simulator/_system/_applications/_red_applications/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/tests/unit_tests/_primaite/_simulator/_system/_applications/_red_applications/test_c2_suite.py b/tests/unit_tests/_primaite/_simulator/_system/_applications/_red_applications/test_c2_suite.py index 4ff387ce..12dddf67 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/_applications/_red_applications/test_c2_suite.py +++ b/tests/unit_tests/_primaite/_simulator/_system/_applications/_red_applications/test_c2_suite.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import pytest from primaite.simulator.network.container import Network diff --git a/tests/unit_tests/_primaite/_simulator/_system/_applications/_red_applications/test_data_manipulation_bot.py b/tests/unit_tests/_primaite/_simulator/_system/_applications/_red_applications/test_data_manipulation_bot.py index 6e9ee224..34a29cd0 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/_applications/_red_applications/test_data_manipulation_bot.py +++ b/tests/unit_tests/_primaite/_simulator/_system/_applications/_red_applications/test_data_manipulation_bot.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import pytest from primaite.simulator.network.hardware.base import Node diff --git a/tests/unit_tests/_primaite/_simulator/_system/_applications/_red_applications/test_dos_bot.py b/tests/unit_tests/_primaite/_simulator/_system/_applications/_red_applications/test_dos_bot.py index 9d8b7809..e9762476 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/_applications/_red_applications/test_dos_bot.py +++ b/tests/unit_tests/_primaite/_simulator/_system/_applications/_red_applications/test_dos_bot.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address import pytest diff --git a/tests/unit_tests/_primaite/_simulator/_system/_applications/test_application_actions.py b/tests/unit_tests/_primaite/_simulator/_system/_applications/test_application_actions.py index a69dc844..0e9c536c 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/_applications/test_application_actions.py +++ b/tests/unit_tests/_primaite/_simulator/_system/_applications/test_application_actions.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from primaite.simulator.system.applications.application import Application, ApplicationOperatingState diff --git a/tests/unit_tests/_primaite/_simulator/_system/_applications/test_application_registry.py b/tests/unit_tests/_primaite/_simulator/_system/_applications/test_application_registry.py index 16a4c9ad..f97e915e 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/_applications/test_application_registry.py +++ b/tests/unit_tests/_primaite/_simulator/_system/_applications/test_application_registry.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import pytest from primaite.simulator.system.applications.application import Application diff --git a/tests/unit_tests/_primaite/_simulator/_system/_applications/test_applications.py b/tests/unit_tests/_primaite/_simulator/_system/_applications/test_applications.py index dd29f18e..aef5d6d1 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/_applications/test_applications.py +++ b/tests/unit_tests/_primaite/_simulator/_system/_applications/test_applications.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from primaite.simulator.system.applications.application import ApplicationOperatingState from primaite.simulator.system.software import SoftwareHealthState diff --git a/tests/unit_tests/_primaite/_simulator/_system/_applications/test_database_client.py b/tests/unit_tests/_primaite/_simulator/_system/_applications/test_database_client.py index 5917fde7..e456ed78 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/_applications/test_database_client.py +++ b/tests/unit_tests/_primaite/_simulator/_system/_applications/test_database_client.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address from typing import Tuple from uuid import uuid4 diff --git a/tests/unit_tests/_primaite/_simulator/_system/_applications/test_web_browser.py b/tests/unit_tests/_primaite/_simulator/_system/_applications/test_web_browser.py index f78b3261..f1be475a 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/_applications/test_web_browser.py +++ b/tests/unit_tests/_primaite/_simulator/_system/_applications/test_web_browser.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import pytest from primaite.simulator.network.hardware.node_operating_state import NodeOperatingState diff --git a/tests/unit_tests/_primaite/_simulator/_system/_services/__init__.py b/tests/unit_tests/_primaite/_simulator/_system/_services/__init__.py index 836b79af..be6c00e7 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/_services/__init__.py +++ b/tests/unit_tests/_primaite/_simulator/_system/_services/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/tests/unit_tests/_primaite/_simulator/_system/_services/test_database.py b/tests/unit_tests/_primaite/_simulator/_system/_services/test_database.py index ef165c8f..9e7ab1d2 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/_services/test_database.py +++ b/tests/unit_tests/_primaite/_simulator/_system/_services/test_database.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import pytest from primaite.simulator.network.hardware.base import Node diff --git a/tests/unit_tests/_primaite/_simulator/_system/_services/test_dns_client.py b/tests/unit_tests/_primaite/_simulator/_system/_services/test_dns_client.py index 1bc5b353..db7e8d58 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/_services/test_dns_client.py +++ b/tests/unit_tests/_primaite/_simulator/_system/_services/test_dns_client.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address import pytest diff --git a/tests/unit_tests/_primaite/_simulator/_system/_services/test_dns_server.py b/tests/unit_tests/_primaite/_simulator/_system/_services/test_dns_server.py index 3bc2b1a4..c64602c0 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/_services/test_dns_server.py +++ b/tests/unit_tests/_primaite/_simulator/_system/_services/test_dns_server.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address import pytest diff --git a/tests/unit_tests/_primaite/_simulator/_system/_services/test_ftp_client.py b/tests/unit_tests/_primaite/_simulator/_system/_services/test_ftp_client.py index d3e679db..95788834 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/_services/test_ftp_client.py +++ b/tests/unit_tests/_primaite/_simulator/_system/_services/test_ftp_client.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from ipaddress import IPv4Address import pytest diff --git a/tests/unit_tests/_primaite/_simulator/_system/_services/test_ftp_server.py b/tests/unit_tests/_primaite/_simulator/_system/_services/test_ftp_server.py index 37c3d019..291cdede 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/_services/test_ftp_server.py +++ b/tests/unit_tests/_primaite/_simulator/_system/_services/test_ftp_server.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import pytest from primaite.simulator.file_system.file_system_item_abc import FileSystemItemHealthStatus diff --git a/tests/unit_tests/_primaite/_simulator/_system/_services/test_service_actions.py b/tests/unit_tests/_primaite/_simulator/_system/_services/test_service_actions.py index 60cd2422..537beb8b 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/_services/test_service_actions.py +++ b/tests/unit_tests/_primaite/_simulator/_system/_services/test_service_actions.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from primaite.simulator.system.services.service import Service, ServiceOperatingState from primaite.simulator.system.software import SoftwareHealthState diff --git a/tests/unit_tests/_primaite/_simulator/_system/_services/test_services.py b/tests/unit_tests/_primaite/_simulator/_system/_services/test_services.py index ad6fe135..8c12adaa 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/_services/test_services.py +++ b/tests/unit_tests/_primaite/_simulator/_system/_services/test_services.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from uuid import uuid4 import pytest diff --git a/tests/unit_tests/_primaite/_simulator/_system/_services/test_terminal.py b/tests/unit_tests/_primaite/_simulator/_system/_services/test_terminal.py index 08bef92d..9b6a4bf3 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/_services/test_terminal.py +++ b/tests/unit_tests/_primaite/_simulator/_system/_services/test_terminal.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import Tuple from uuid import uuid4 diff --git a/tests/unit_tests/_primaite/_simulator/_system/_services/test_web_server.py b/tests/unit_tests/_primaite/_simulator/_system/_services/test_web_server.py index 606a195c..54f86ec8 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/_services/test_web_server.py +++ b/tests/unit_tests/_primaite/_simulator/_system/_services/test_web_server.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import pytest from primaite.simulator.network.hardware.node_operating_state import NodeOperatingState diff --git a/tests/unit_tests/_primaite/_simulator/_system/core/test_sys_log.py b/tests/unit_tests/_primaite/_simulator/_system/core/test_sys_log.py index 5a734b6e..053211cd 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/core/test_sys_log.py +++ b/tests/unit_tests/_primaite/_simulator/_system/core/test_sys_log.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from uuid import uuid4 import pytest diff --git a/tests/unit_tests/_primaite/_simulator/_system/test_software.py b/tests/unit_tests/_primaite/_simulator/_system/test_software.py index a203a636..300f8d9d 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/test_software.py +++ b/tests/unit_tests/_primaite/_simulator/_system/test_software.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import Dict import pytest diff --git a/tests/unit_tests/_primaite/_simulator/test_core.py b/tests/unit_tests/_primaite/_simulator/test_core.py index 271375eb..02960978 100644 --- a/tests/unit_tests/_primaite/_simulator/test_core.py +++ b/tests/unit_tests/_primaite/_simulator/test_core.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from typing import Callable, Dict, List, Literal, Tuple import pytest diff --git a/tests/unit_tests/_primaite/_simulator/test_sim_container.py b/tests/unit_tests/_primaite/_simulator/test_sim_container.py index f482d7e6..fe702307 100644 --- a/tests/unit_tests/_primaite/_simulator/test_sim_container.py +++ b/tests/unit_tests/_primaite/_simulator/test_sim_container.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from primaite.simulator.sim_container import Simulation diff --git a/tests/unit_tests/_primaite/_utils/__init__.py b/tests/unit_tests/_primaite/_utils/__init__.py index 836b79af..be6c00e7 100644 --- a/tests/unit_tests/_primaite/_utils/__init__.py +++ b/tests/unit_tests/_primaite/_utils/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/tests/unit_tests/_primaite/_utils/_validation/__init__.py b/tests/unit_tests/_primaite/_utils/_validation/__init__.py index 836b79af..be6c00e7 100644 --- a/tests/unit_tests/_primaite/_utils/_validation/__init__.py +++ b/tests/unit_tests/_primaite/_utils/_validation/__init__.py @@ -1 +1 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK diff --git a/tests/unit_tests/_primaite/_utils/_validation/test_ip_protocol.py b/tests/unit_tests/_primaite/_utils/_validation/test_ip_protocol.py index 7acbe4a7..27829570 100644 --- a/tests/unit_tests/_primaite/_utils/_validation/test_ip_protocol.py +++ b/tests/unit_tests/_primaite/_utils/_validation/test_ip_protocol.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import pytest from primaite.utils.validation.ip_protocol import IPProtocol, is_valid_protocol, PROTOCOL_LOOKUP, protocol_validator diff --git a/tests/unit_tests/_primaite/_utils/_validation/test_port.py b/tests/unit_tests/_primaite/_utils/_validation/test_port.py index 2e30ab76..6a8a2429 100644 --- a/tests/unit_tests/_primaite/_utils/_validation/test_port.py +++ b/tests/unit_tests/_primaite/_utils/_validation/test_port.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK import pytest from primaite.utils.validation.port import is_valid_port, Port, PORT_LOOKUP, port_validator diff --git a/tests/unit_tests/_primaite/_utils/test_dict_enum_keys_conversion.py b/tests/unit_tests/_primaite/_utils/test_dict_enum_keys_conversion.py index d0a64ece..1a1848ac 100644 --- a/tests/unit_tests/_primaite/_utils/test_dict_enum_keys_conversion.py +++ b/tests/unit_tests/_primaite/_utils/test_dict_enum_keys_conversion.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK from primaite.utils.converters import convert_dict_enum_keys_to_enum_values from primaite.utils.validation.ip_protocol import PROTOCOL_LOOKUP from primaite.utils.validation.port import PORT_LOOKUP From 0ee454b13ef69f3ba58064d1edd2573720c51a2a Mon Sep 17 00:00:00 2001 From: Charlie Crane Date: Fri, 3 Jan 2025 14:53:54 +0000 Subject: [PATCH 54/58] #2912 - Rename actions/config.py to actions/software.py --- .../how_to_guides/extensible_actions.rst | 69 +------------------ src/primaite/game/agent/actions/__init__.py | 6 +- src/primaite/game/agent/actions/abstract.py | 2 +- src/primaite/game/agent/actions/acl.py | 2 +- .../game/agent/actions/application.py | 2 +- src/primaite/game/agent/actions/file.py | 2 +- src/primaite/game/agent/actions/folder.py | 2 +- src/primaite/game/agent/actions/host_nic.py | 2 +- src/primaite/game/agent/actions/manager.py | 2 +- src/primaite/game/agent/actions/network.py | 2 +- src/primaite/game/agent/actions/node.py | 2 +- src/primaite/game/agent/actions/service.py | 2 +- src/primaite/game/agent/actions/session.py | 2 +- .../agent/actions/{config.py => software.py} | 2 +- src/primaite/game/agent/rewards.py | 3 +- .../actions/test_configure_actions.py | 2 +- 16 files changed, 19 insertions(+), 85 deletions(-) rename src/primaite/game/agent/actions/{config.py => software.py} (99%) diff --git a/docs/source/how_to_guides/extensible_actions.rst b/docs/source/how_to_guides/extensible_actions.rst index 6e44a905..f2e053aa 100644 --- a/docs/source/how_to_guides/extensible_actions.rst +++ b/docs/source/how_to_guides/extensible_actions.rst @@ -1,70 +1,3 @@ .. only:: comment - © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK - -.. _about: - -Extensible Actions -****************** - -Actions defined within PrimAITE have been updated to allow for easier creation of new bespoke actions, without the need to make changes to the ActionManager class within the core PrimAITE repository. - - -Developing Actions for PrimAITE -=============================== - -When developing new actions for PrimAITE, it's important to ensure new actions inherit from the AbstractAction class. This is so that the `ActionManager` has visibility -of the new action through the `AbstractAction` registry attribute. This also removes the need for actions to contain an `__init__` method. - -New actions to be used within PrimAITE require: - -#. **ConfigSchema**: - - This should be a nested class that defines the required configuration items for the new action. - - .. code-block:: python - - class ExampleAction(AbstractAction, identifier="Example_action"): - """An example action for demonstration purposes.""" - - config: "ExampleAction.ConfigSchema" - - class ConfigSchema(AbstractAction.ConfigSchema): - """The configuration schema with all attributes expected goes here.""" - target_application: str - - The ConfigSchema is used when the class is called to form the action, within the `form_request` method, detailed below. - - -#. **Unique Identifier**: - - New actions should have a Unique identifier when declared. This is used by the `ActionManager` when forming/processing action commands from agents. See the example code block in ConfigSchema for how this should be implemented. - -#. **form_request method**: - - New actions need a `form_request()` method, to convert the action into a ``Requestformat`` that can be ingested by PrimAITE's `RequestManager`. - The below is an example of how this is done, taken from the `NodeFolderCreateAction`. - - .. code-block:: python - - @classmethod - def form_request(cls, config: ConfigSchema) -> RequestFormat: - """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - if config.node_name is None or config.folder_name is None: - return ["do_nothing"] - return [ - "network", - "node", - config.node_name, - "file_system", - config.verb, - "folder", - config.folder_name, - ] - -There is no longer a need for a `from_config()` method to be defined within new actions, as this is handled within the base `AbstractAction` class. - -Changes to YAML file. -===================== - -Action identifiers now follow the snake_case naming style, instead of the MACRO_CASE that has been seen previously. Please review any custom YAML files for any issues seen. This should be backwards compatible. + © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK diff --git a/src/primaite/game/agent/actions/__init__.py b/src/primaite/game/agent/actions/__init__.py index 016a09ba..8517ded8 100644 --- a/src/primaite/game/agent/actions/__init__.py +++ b/src/primaite/game/agent/actions/__init__.py @@ -1,10 +1,9 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from primaite.game.agent.actions import ( abstract, acl, application, - config, file, folder, host_nic, @@ -13,6 +12,7 @@ from primaite.game.agent.actions import ( node, service, session, + software, ) from primaite.game.agent.actions.manager import ActionManager @@ -20,7 +20,7 @@ __all__ = ( "abstract", "acl", "application", - "config", + "software", "file", "folder", "host_nic", diff --git a/src/primaite/game/agent/actions/abstract.py b/src/primaite/game/agent/actions/abstract.py index 8c332d5e..15c9b4cb 100644 --- a/src/primaite/game/agent/actions/abstract.py +++ b/src/primaite/game/agent/actions/abstract.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from __future__ import annotations from abc import ABC diff --git a/src/primaite/game/agent/actions/acl.py b/src/primaite/game/agent/actions/acl.py index d2846ddb..6fefeeda 100644 --- a/src/primaite/game/agent/actions/acl.py +++ b/src/primaite/game/agent/actions/acl.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from __future__ import annotations from ipaddress import IPv4Address diff --git a/src/primaite/game/agent/actions/application.py b/src/primaite/game/agent/actions/application.py index 91e34eae..96609f93 100644 --- a/src/primaite/game/agent/actions/application.py +++ b/src/primaite/game/agent/actions/application.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import ClassVar from primaite.game.agent.actions.abstract import AbstractAction diff --git a/src/primaite/game/agent/actions/file.py b/src/primaite/game/agent/actions/file.py index b5e47c8a..e5ca1c46 100644 --- a/src/primaite/game/agent/actions/file.py +++ b/src/primaite/game/agent/actions/file.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import ClassVar from primaite.game.agent.actions.manager import AbstractAction diff --git a/src/primaite/game/agent/actions/folder.py b/src/primaite/game/agent/actions/folder.py index a27ca89b..d1fd5ef1 100644 --- a/src/primaite/game/agent/actions/folder.py +++ b/src/primaite/game/agent/actions/folder.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import ClassVar from primaite.game.agent.actions.manager import AbstractAction diff --git a/src/primaite/game/agent/actions/host_nic.py b/src/primaite/game/agent/actions/host_nic.py index e2adf7d7..7b290103 100644 --- a/src/primaite/game/agent/actions/host_nic.py +++ b/src/primaite/game/agent/actions/host_nic.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import ClassVar from primaite.game.agent.actions.manager import AbstractAction diff --git a/src/primaite/game/agent/actions/manager.py b/src/primaite/game/agent/actions/manager.py index a6a4f5a6..b612d9ce 100644 --- a/src/primaite/game/agent/actions/manager.py +++ b/src/primaite/game/agent/actions/manager.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK """yaml example. agents: diff --git a/src/primaite/game/agent/actions/network.py b/src/primaite/game/agent/actions/network.py index 346da9b7..fa1c4451 100644 --- a/src/primaite/game/agent/actions/network.py +++ b/src/primaite/game/agent/actions/network.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import ClassVar diff --git a/src/primaite/game/agent/actions/node.py b/src/primaite/game/agent/actions/node.py index 480cb8da..c6b74f2e 100644 --- a/src/primaite/game/agent/actions/node.py +++ b/src/primaite/game/agent/actions/node.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from abc import abstractmethod from typing import ClassVar, List, Optional, Union diff --git a/src/primaite/game/agent/actions/service.py b/src/primaite/game/agent/actions/service.py index 7ccffb0a..fa47ffb1 100644 --- a/src/primaite/game/agent/actions/service.py +++ b/src/primaite/game/agent/actions/service.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import ClassVar from primaite.game.agent.actions.manager import AbstractAction diff --git a/src/primaite/game/agent/actions/session.py b/src/primaite/game/agent/actions/session.py index a0805a49..1191987b 100644 --- a/src/primaite/game/agent/actions/session.py +++ b/src/primaite/game/agent/actions/session.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from abc import abstractmethod from primaite.game.agent.actions.manager import AbstractAction diff --git a/src/primaite/game/agent/actions/config.py b/src/primaite/game/agent/actions/software.py similarity index 99% rename from src/primaite/game/agent/actions/config.py rename to src/primaite/game/agent/actions/software.py index 050e9b94..760e8dfa 100644 --- a/src/primaite/game/agent/actions/config.py +++ b/src/primaite/game/agent/actions/software.py @@ -1,4 +1,4 @@ -# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK +# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from typing import List, Optional, Union diff --git a/src/primaite/game/agent/rewards.py b/src/primaite/game/agent/rewards.py index f065d888..8f0bd24b 100644 --- a/src/primaite/game/agent/rewards.py +++ b/src/primaite/game/agent/rewards.py @@ -381,15 +381,16 @@ class ActionPenalty(AbstractReward, identifier="ACTION_PENALTY"): class ConfigSchema(AbstractReward.ConfigSchema): """Config schema for ActionPenalty. + :param action_penalty: Reward to give agents for taking any action except do_nothing :type action_penalty: float :param do_nothing_penalty: Reward to give agent for taking the do_nothing action :type do_nothing_penalty: float """ + action_penalty: float = -1.0 do_nothing_penalty: float = 0.0 - def calculate(self, state: Dict, last_action_response: "AgentHistoryItem") -> float: """Calculate the penalty to be applied. diff --git a/tests/integration_tests/game_layer/actions/test_configure_actions.py b/tests/integration_tests/game_layer/actions/test_configure_actions.py index 8c97573a..338bd049 100644 --- a/tests/integration_tests/game_layer/actions/test_configure_actions.py +++ b/tests/integration_tests/game_layer/actions/test_configure_actions.py @@ -4,7 +4,7 @@ from ipaddress import IPv4Address import pytest from pydantic import ValidationError -from primaite.game.agent.actions.config import ( +from primaite.game.agent.actions.software import ( ConfigureDatabaseClientAction, ConfigureDoSBotAction, ConfigureRansomwareScriptAction, From a0a5f2ca38ddd6f2cb6cba3acccd7df15358266d Mon Sep 17 00:00:00 2001 From: Charlie Crane Date: Fri, 3 Jan 2025 15:03:01 +0000 Subject: [PATCH 55/58] #2912 - Review comment actions following commit revertions --- src/primaite/game/agent/actions/manager.py | 2 -- tests/conftest.py | 27 ---------------------- 2 files changed, 29 deletions(-) diff --git a/src/primaite/game/agent/actions/manager.py b/src/primaite/game/agent/actions/manager.py index b612d9ce..625d8cec 100644 --- a/src/primaite/game/agent/actions/manager.py +++ b/src/primaite/game/agent/actions/manager.py @@ -80,8 +80,6 @@ class ActionManager: self.action_map = {i: (a["action"], a["options"]) for i, a in act_map.items()} # make sure all numbers between 0 and N are represented as dict keys in action map assert all([i in self.action_map.keys() for i in range(len(self.action_map))]) - self.node_names: List[str] = [n["node_name"] for n in nodes] - """List of node names in this action space. The list order is the mapping between node index and node name.""" def get_action(self, action: int) -> Tuple[str, Dict]: """Produce action in CAOS format.""" diff --git a/tests/conftest.py b/tests/conftest.py index 59fe025f..0d73aa07 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -471,33 +471,6 @@ def game_and_agent(): action_space = ActionManager( actions=actions, # ALL POSSIBLE ACTIONS - nodes=[ - { - "node_name": "client_1", - "applications": [ - {"application_name": "WebBrowser"}, - {"application_name": "DoSBot"}, - {"application_name": "C2Server"}, - ], - "folders": [{"folder_name": "downloads", "files": [{"file_name": "cat.png"}]}], - }, - { - "node_name": "server_1", - "services": [{"service_name": "DNSServer"}], - "applications": [{"application_name": "C2Beacon"}], - }, - {"node_name": "server_2", "services": [{"service_name": "WebServer"}]}, - {"node_name": "router"}, - ], - max_folders_per_node=2, - max_files_per_folder=2, - max_services_per_node=2, - max_applications_per_node=3, - max_nics_per_node=2, - max_acl_rules=10, - protocols=["TCP", "UDP", "ICMP"], - ports=["HTTP", "DNS", "ARP"], - ip_list=["10.0.1.1", "10.0.1.2", "10.0.2.1", "10.0.2.2", "10.0.2.3"], act_map={}, ) observation_space = ObservationManager(NestedObservation(components={})) From 3cca3d4a5ccfce7d6db40acc2bd2795ce5ca7e81 Mon Sep 17 00:00:00 2001 From: Charlie Crane Date: Mon, 13 Jan 2025 16:12:16 +0000 Subject: [PATCH 56/58] #2912 - Actioning review comments. Identifiers have been removed from AbstractActions, _legacy folder has been deleted and correction to IPV4Address type hints --- src/primaite/_legacy/actions.py | 1250 ----------------- src/primaite/game/agent/actions/abstract.py | 2 +- src/primaite/game/agent/actions/acl.py | 10 +- .../game/agent/actions/application.py | 3 +- src/primaite/game/agent/actions/file.py | 3 +- src/primaite/game/agent/actions/folder.py | 3 +- src/primaite/game/agent/actions/host_nic.py | 3 +- src/primaite/game/agent/actions/manager.py | 3 - .../simulator/system/core/software_manager.py | 4 - src/primaite/utils/validation/ipv4_address.py | 2 +- .../_primaite/_game/_agent/test_actions.py | 2 +- .../_game/_agent/test_sticky_rewards.py | 2 +- 12 files changed, 17 insertions(+), 1270 deletions(-) delete mode 100644 src/primaite/_legacy/actions.py diff --git a/src/primaite/_legacy/actions.py b/src/primaite/_legacy/actions.py deleted file mode 100644 index d2457a20..00000000 --- a/src/primaite/_legacy/actions.py +++ /dev/null @@ -1,1250 +0,0 @@ -# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK -""" -This module contains the ActionManager class which belongs to the Agent class. - -An agent's action space is made up of a collection of actions. Each action is an instance of a subclass of -AbstractAction. The ActionManager is responsible for: - 1. Creating the action space from a list of action types. - 2. Converting an integer action choice into a specific action and parameter choice. - 3. Converting an action and parameter choice into a request which can be ingested by the PrimAITE simulation. This - ensures that requests conform to the simulator's request format. -""" -from abc import abstractmethod -from typing import Dict, List, Literal, Optional, Union - -from pydantic import BaseModel, ConfigDict, Field, field_validator, ValidationInfo - -from primaite import getLogger -from primaite.game.agent.actions.manager import AbstractAction, ActionManager -from primaite.game.agent.actions.service import NodeServiceAbstractAction -from primaite.interface.request import RequestFormat - -_LOGGER = getLogger(__name__) - - -class NodeServiceScanAction(NodeServiceAbstractAction): - """Action which scans a service.""" - - def __init__(self, manager: "ActionManager", num_nodes: int, num_services: int, **kwargs) -> None: - super().__init__(manager=manager, num_nodes=num_nodes, num_services=num_services) - self.verb: str = "scan" - - -class NodeServiceStopAction(NodeServiceAbstractAction): - """Action which stops a service.""" - - def __init__(self, manager: "ActionManager", num_nodes: int, num_services: int, **kwargs) -> None: - super().__init__(manager=manager, num_nodes=num_nodes, num_services=num_services) - self.verb: str = "stop" - - -class NodeServiceStartAction(NodeServiceAbstractAction): - """Action which starts a service.""" - - def __init__(self, manager: "ActionManager", num_nodes: int, num_services: int, **kwargs) -> None: - super().__init__(manager=manager, num_nodes=num_nodes, num_services=num_services) - self.verb: str = "start" - - -class NodeServicePauseAction(NodeServiceAbstractAction): - """Action which pauses a service.""" - - def __init__(self, manager: "ActionManager", num_nodes: int, num_services: int, **kwargs) -> None: - super().__init__(manager=manager, num_nodes=num_nodes, num_services=num_services) - self.verb: str = "pause" - - -class NodeServiceResumeAction(NodeServiceAbstractAction): - """Action which resumes a service.""" - - def __init__(self, manager: "ActionManager", num_nodes: int, num_services: int, **kwargs) -> None: - super().__init__(manager=manager, num_nodes=num_nodes, num_services=num_services) - self.verb: str = "resume" - - -class NodeServiceRestartAction(NodeServiceAbstractAction): - """Action which restarts a service.""" - - def __init__(self, manager: "ActionManager", num_nodes: int, num_services: int, **kwargs) -> None: - super().__init__(manager=manager, num_nodes=num_nodes, num_services=num_services) - self.verb: str = "restart" - - -class NodeServiceDisableAction(NodeServiceAbstractAction): - """Action which disables a service.""" - - def __init__(self, manager: "ActionManager", num_nodes: int, num_services: int, **kwargs) -> None: - super().__init__(manager=manager, num_nodes=num_nodes, num_services=num_services) - self.verb: str = "disable" - - -class NodeServiceEnableAction(NodeServiceAbstractAction): - """Action which enables a service.""" - - def __init__(self, manager: "ActionManager", num_nodes: int, num_services: int, **kwargs) -> None: - super().__init__(manager=manager, num_nodes=num_nodes, num_services=num_services) - self.verb: str = "enable" - - -class NodeServiceFixAction(NodeServiceAbstractAction): - """Action which fixes a service.""" - - def __init__(self, manager: "ActionManager", num_nodes: int, num_services: int, **kwargs) -> None: - super().__init__(manager=manager, num_nodes=num_nodes, num_services=num_services) - self.verb: str = "fix" - - -class NodeApplicationAbstractAction(AbstractAction): - """ - Base class for application actions. - - Any action which applies to an application and uses node_id and application_id as its only two parameters can - inherit from this base class. - """ - - @abstractmethod - def __init__(self, manager: "ActionManager", num_nodes: int, num_applications: int, **kwargs) -> None: - super().__init__(manager=manager) - self.shape: Dict[str, int] = {"node_id": num_nodes, "application_id": num_applications} - self.verb: str # define but don't initialise: defends against children classes not defining this - - def form_request(self, node_id: int, application_id: int) -> RequestFormat: - """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - node_name = self.manager.get_node_name_by_idx(node_id) - application_name = self.manager.get_application_name_by_idx(node_id, application_id) - if node_name is None or application_name is None: - return ["do_nothing"] - return ["network", "node", node_name, "application", application_name, self.verb] - - -class NodeApplicationExecuteAction(NodeApplicationAbstractAction): - """Action which executes an application.""" - - def __init__(self, manager: "ActionManager", num_nodes: int, num_applications: int, **kwargs) -> None: - super().__init__(manager=manager, num_nodes=num_nodes, num_applications=num_applications) - self.verb: str = "execute" - - -class NodeApplicationScanAction(NodeApplicationAbstractAction): - """Action which scans an application.""" - - def __init__(self, manager: "ActionManager", num_nodes: int, num_applications: int, **kwargs) -> None: - super().__init__(manager=manager, num_nodes=num_nodes, num_applications=num_applications) - self.verb: str = "scan" - - -class NodeApplicationCloseAction(NodeApplicationAbstractAction): - """Action which closes an application.""" - - def __init__(self, manager: "ActionManager", num_nodes: int, num_applications: int, **kwargs) -> None: - super().__init__(manager=manager, num_nodes=num_nodes, num_applications=num_applications) - self.verb: str = "close" - - -class NodeApplicationFixAction(NodeApplicationAbstractAction): - """Action which fixes an application.""" - - def __init__(self, manager: "ActionManager", num_nodes: int, num_applications: int, **kwargs) -> None: - super().__init__(manager=manager, num_nodes=num_nodes, num_applications=num_applications) - self.verb: str = "fix" - - -class NodeApplicationInstallAction(AbstractAction): - """Action which installs an application.""" - - def __init__(self, manager: "ActionManager", num_nodes: int, **kwargs) -> None: - super().__init__(manager=manager) - self.shape: Dict[str, int] = {"node_id": num_nodes} - - def form_request(self, node_id: int, application_name: str) -> RequestFormat: - """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - node_name = self.manager.get_node_name_by_idx(node_id) - if node_name is None: - return ["do_nothing"] - return [ - "network", - "node", - node_name, - "software_manager", - "application", - "install", - application_name, - ] - - -class ConfigureDatabaseClientAction(AbstractAction): - """Action which sets config parameters for a database client on a node.""" - - class _Opts(BaseModel): - """Schema for options that can be passed to this action.""" - - model_config = ConfigDict(extra="forbid") - server_ip_address: Optional[str] = None - server_password: Optional[str] = None - - def __init__(self, manager: "ActionManager", **kwargs) -> None: - super().__init__(manager=manager) - - def form_request(self, node_id: int, config: Dict) -> RequestFormat: - """Return the action formatted as a request that can be ingested by the simulation.""" - node_name = self.manager.get_node_name_by_idx(node_id) - if node_name is None: - return ["do_nothing"] - ConfigureDatabaseClientAction._Opts.model_validate(config) # check that options adhere to schema - return ["network", "node", node_name, "application", "DatabaseClient", "configure", config] - - -class ConfigureRansomwareScriptAction(AbstractAction): - """Action which sets config parameters for a ransomware script on a node.""" - - class _Opts(BaseModel, AbstractAction.ConfigSchema): - """Schema for options that can be passed to this option.""" - - model_config = ConfigDict(extra="forbid") - server_ip_address: Optional[str] = None - server_password: Optional[str] = None - payload: Optional[str] = None - - def __init__(self, manager: "ActionManager", **kwargs) -> None: - super().__init__(manager=manager) - - def form_request(self, config: _Opts) -> RequestFormat: - """Return the action formatted as a request that can be ingested by the simulation.""" - if config.node_name is None: - return ["do_nothing"] - ConfigureRansomwareScriptAction._Opts.model_validate(config) # check that options adhere to schema - return [ - "network", - "node", - config.node_name, - "application", - "RansomwareScript", - "configure", - config.model_config, - ] - - -class ConfigureDoSBotAction(AbstractAction): - """Action which sets config parameters for a DoS bot on a node.""" - - class _Opts(BaseModel): - """Schema for options that can be passed to this action.""" - - model_config = ConfigDict(extra="forbid") - target_ip_address: Optional[str] = None - target_port: Optional[str] = None - payload: Optional[str] = None - repeat: Optional[bool] = None - port_scan_p_of_success: Optional[float] = None - dos_intensity: Optional[float] = None - max_sessions: Optional[int] = None - - def __init__(self, manager: "ActionManager", **kwargs) -> None: - super().__init__(manager=manager) - - def form_request(self, node_id: int, config: Dict) -> RequestFormat: - """Return the action formatted as a request that can be ingested by the simulation.""" - node_name = self.manager.get_node_name_by_idx(node_id) - if node_name is None: - return ["do_nothing"] - self._Opts.model_validate(config) # check that options adhere to schema - return ["network", "node", node_name, "application", "DoSBot", "configure", config] - - -class NodeApplicationRemoveAction(AbstractAction): - """Action which removes/uninstalls an application.""" - - def __init__(self, manager: "ActionManager", num_nodes: int, **kwargs) -> None: - super().__init__(manager=manager) - self.shape: Dict[str, int] = {"node_id": num_nodes} - - def form_request(self, node_id: int, application_name: str) -> RequestFormat: - """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - node_name = self.manager.get_node_name_by_idx(node_id) - if node_name is None: - return ["do_nothing"] - return ["network", "node", node_name, "software_manager", "application", "uninstall", application_name] - - -class NodeFolderAbstractAction(AbstractAction): - """ - Base class for folder actions. - - Any action which applies to a folder and uses node_id and folder_id as its only two parameters can inherit from - this base class. - """ - - @abstractmethod - def __init__(self, manager: "ActionManager", num_nodes: int, num_folders: int, **kwargs) -> None: - super().__init__(manager=manager) - self.shape: Dict[str, int] = {"node_id": num_nodes, "folder_id": num_folders} - self.verb: str # define but don't initialise: defends against children classes not defining this - - def form_request(self, node_id: int, folder_id: int) -> RequestFormat: - """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - node_name = self.manager.get_node_name_by_idx(node_id) - folder_name = self.manager.get_folder_name_by_idx(node_idx=node_id, folder_idx=folder_id) - if node_name is None or folder_name is None: - return ["do_nothing"] - return ["network", "node", node_name, "file_system", "folder", folder_name, self.verb] - - -class NodeFolderScanAction(NodeFolderAbstractAction): - """Action which scans a folder.""" - - def __init__(self, manager: "ActionManager", node_name: str, folder_name: str, **kwargs) -> None: - super().__init__(manager, node_name=node_name, folder_name=folder_name, **kwargs) - self.verb: str = "scan" - - -class NodeFolderCheckhashAction(NodeFolderAbstractAction): - """Action which checks the hash of a folder.""" - - def __init__(self, manager: "ActionManager", num_nodes: int, num_folders: int, **kwargs) -> None: - super().__init__(manager, num_nodes=num_nodes, num_folders=num_folders, **kwargs) - self.verb: str = "checkhash" - - -class NodeFolderRepairAction(NodeFolderAbstractAction): - """Action which repairs a folder.""" - - def __init__(self, manager: "ActionManager", num_nodes: int, num_folders: int, **kwargs) -> None: - super().__init__(manager, num_nodes=num_nodes, num_folders=num_folders, **kwargs) - self.verb: str = "repair" - - -class NodeFolderRestoreAction(NodeFolderAbstractAction): - """Action which restores a folder.""" - - def __init__(self, manager: "ActionManager", num_nodes: int, num_folders: int, **kwargs) -> None: - super().__init__(manager, num_nodes=num_nodes, num_folders=num_folders, **kwargs) - self.verb: str = "restore" - - -class NodeFileCreateAction(AbstractAction): - """Action which creates a new file in a given folder.""" - - def __init__(self, manager: "ActionManager", num_nodes: int, num_folders: int, **kwargs) -> None: - super().__init__(manager, num_nodes=num_nodes, num_folders=num_folders, **kwargs) - self.verb: str = "create" - - def form_request( - self, node_id: int, folder_name: str, file_name: str, force: Optional[bool] = False - ) -> RequestFormat: - """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - node_name = self.manager.get_node_name_by_idx(node_id) - if node_name is None or folder_name is None or file_name is None: - return ["do_nothing"] - return ["network", "node", node_name, "file_system", "create", "file", folder_name, file_name, force] - - -class NodeFolderCreateAction(AbstractAction): - """Action which creates a new folder.""" - - def __init__(self, manager: "ActionManager", num_nodes: int, num_folders: int, **kwargs) -> None: - super().__init__(manager, num_nodes=num_nodes, num_folders=num_folders, **kwargs) - self.verb: str = "create" - - def form_request(self, node_id: int, folder_name: str) -> RequestFormat: - """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - node_name = self.manager.get_node_name_by_idx(node_id) - if node_name is None or folder_name is None: - return ["do_nothing"] - return ["network", "node", node_name, "file_system", "create", "folder", folder_name] - - -class NodeFileAbstractAction(AbstractAction): - """Abstract base class for file actions. - - Any action which applies to a file and uses node_id, folder_id, and file_id as its only three parameters can inherit - from this base class. - """ - - @abstractmethod - def __init__(self, manager: "ActionManager", num_nodes: int, num_folders: int, num_files: int, **kwargs) -> None: - super().__init__(manager=manager) - self.shape: Dict[str, int] = {"node_id": num_nodes, "folder_id": num_folders, "file_id": num_files} - self.verb: str # define but don't initialise: defends against children classes not defining this - - def form_request(self, node_id: int, folder_id: int, file_id: int) -> RequestFormat: - """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - node_name = self.manager.get_node_name_by_idx(node_id) - folder_name = self.manager.get_folder_name_by_idx(node_idx=node_id, folder_idx=folder_id) - file_name = self.manager.get_file_name_by_idx(node_idx=node_id, folder_idx=folder_id, file_idx=file_id) - if node_name is None or folder_name is None or file_name is None: - return ["do_nothing"] - return ["network", "node", node_name, "file_system", "folder", folder_name, "file", file_name, self.verb] - - -class NodeFileScanAction(NodeFileAbstractAction): - """Action which scans a file.""" - - def __init__(self, manager: "ActionManager", num_nodes: int, num_folders: int, num_files: int, **kwargs) -> None: - super().__init__(manager, num_nodes=num_nodes, num_folders=num_folders, num_files=num_files, **kwargs) - self.verb: str = "scan" - - -class NodeFileCheckhashAction(NodeFileAbstractAction): - """Action which checks the hash of a file.""" - - def __init__(self, manager: "ActionManager", num_nodes: int, num_folders: int, num_files: int, **kwargs) -> None: - super().__init__(manager, num_nodes=num_nodes, num_folders=num_folders, num_files=num_files, **kwargs) - self.verb: str = "checkhash" - - -class NodeFileDeleteAction(NodeFileAbstractAction): - """Action which deletes a file.""" - - def __init__(self, manager: "ActionManager", num_nodes: int, num_folders: int, num_files: int, **kwargs) -> None: - super().__init__(manager, num_nodes=num_nodes, num_folders=num_folders, num_files=num_files, **kwargs) - self.verb: str = "delete" - - def form_request(self, node_id: int, folder_id: int, file_id: int) -> RequestFormat: - """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - node_name = self.manager.get_node_name_by_idx(node_id) - folder_name = self.manager.get_folder_name_by_idx(node_idx=node_id, folder_idx=folder_id) - file_name = self.manager.get_file_name_by_idx(node_idx=node_id, folder_idx=folder_id, file_idx=file_id) - if node_name is None or folder_name is None or file_name is None: - return ["do_nothing"] - return ["network", "node", node_name, "file_system", "delete", "file", folder_name, file_name] - - -class NodeFileRepairAction(NodeFileAbstractAction): - """Action which repairs a file.""" - - def __init__(self, manager: "ActionManager", num_nodes: int, num_folders: int, num_files: int, **kwargs) -> None: - super().__init__(manager, num_nodes=num_nodes, num_folders=num_folders, num_files=num_files, **kwargs) - self.verb: str = "repair" - - -class NodeFileRestoreAction(NodeFileAbstractAction): - """Action which restores a file.""" - - def __init__(self, manager: "ActionManager", num_nodes: int, num_folders: int, num_files: int, **kwargs) -> None: - super().__init__(manager, num_nodes=num_nodes, num_folders=num_folders, num_files=num_files, **kwargs) - self.verb: str = "restore" - - -class NodeFileCorruptAction(NodeFileAbstractAction): - """Action which corrupts a file.""" - - def __init__(self, manager: "ActionManager", num_nodes: int, num_folders: int, num_files: int, **kwargs) -> None: - super().__init__(manager, num_nodes=num_nodes, num_folders=num_folders, num_files=num_files, **kwargs) - self.verb: str = "corrupt" - - -class NodeFileAccessAction(AbstractAction): - """Action which increases a file's access count.""" - - def __init__(self, manager: "ActionManager", num_nodes: int, num_folders: int, **kwargs) -> None: - super().__init__(manager, num_nodes=num_nodes, num_folders=num_folders, **kwargs) - self.verb: str = "access" - - def form_request(self, node_id: int, folder_name: str, file_name: str) -> RequestFormat: - """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - node_name = self.manager.get_node_name_by_idx(node_id) - if node_name is None or folder_name is None or file_name is None: - return ["do_nothing"] - return ["network", "node", node_name, "file_system", "access", folder_name, file_name] - - -class NodeAbstractAction(AbstractAction): - """ - Abstract base class for node actions. - - Any action which applies to a node and uses node_id as its only parameter can inherit from this base class. - """ - - config: "NodeAbstractAction.ConfigSchema" - - class ConfigSchema(AbstractAction.ConfigSchema): - """Configuration schema for NodeAbstractAction.""" - - verb: str = "Node_Abstract_Action" - - def form_request(self, node_id: int) -> RequestFormat: - """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - node_name = self.manager.get_node_name_by_idx(node_id) - return ["network", "node", node_name, self.verb] - - -class NodeOSScanAction(NodeAbstractAction): - """Action which scans a node's OS.""" - - def __init__(self, manager: "ActionManager", num_nodes: int, **kwargs) -> None: - super().__init__(manager=manager, num_nodes=num_nodes) - self.verb: str = "scan" - - -class NodeShutdownAction(NodeAbstractAction): - """Action which shuts down a node.""" - - config: "NodeShutdownAction.ConfigSchema" - - class ConfigSchema(NodeAbstractAction.ConfigSchema): - """Configuration Schema for NodeShutdownAction.""" - - verb: str = "shutdown" - - -class NodeStartupAction(NodeAbstractAction): - """Action which starts up a node.""" - - def __init__(self, manager: "ActionManager", num_nodes: int, **kwargs) -> None: - super().__init__(manager=manager, num_nodes=num_nodes) - self.verb: str = "startup" - - -class NodeResetAction(NodeAbstractAction): - """Action which resets a node.""" - - def __init__(self, manager: "ActionManager", num_nodes: int, **kwargs) -> None: - super().__init__(manager=manager, num_nodes=num_nodes) - self.verb: str = "reset" - - -class RouterACLAddRuleAction(AbstractAction): - """Action which adds a rule to a router's ACL.""" - - class ACLRuleOptions(BaseModel): - """Validator for ACL_ADD_RULE options.""" - - target_router: str - """On which router to add the rule, must be specified.""" - position: int - """At what position to add the rule, must be specified.""" - permission: Literal[1, 2] - """Whether to allow or deny traffic, must be specified. 1 = PERMIT, 2 = DENY.""" - source_ip_id: int = Field(default=1, ge=1) - """Rule source IP address. By default, all ip addresses.""" - source_wildcard_id: int = Field(default=0, ge=0) - """Rule source IP wildcard. By default, use the wildcard at index 0 from action manager.""" - source_port_id: int = Field(default=1, ge=1) - """Rule source port. By default, all source ports.""" - dest_ip_id: int = Field(default=1, ge=1) - """Rule destination IP address. By default, all ip addresses.""" - dest_wildcard_id: int = Field(default=0, ge=0) - """Rule destination IP wildcard. By default, use the wildcard at index 0 from action manager.""" - dest_port_id: int = Field(default=1, ge=1) - """Rule destination port. By default, all destination ports.""" - protocol_id: int = Field(default=1, ge=1) - """Rule protocol. By default, all protocols.""" - - @field_validator( - "source_ip_id", - "source_port_id", - "source_wildcard_id", - "dest_ip_id", - "dest_port_id", - "dest_wildcard_id", - "protocol_id", - mode="before", - ) - @classmethod - def not_none(cls, v: str, info: ValidationInfo) -> int: - """If None is passed, use the default value instead.""" - if v is None: - return cls.model_fields[info.field_name].default - return v - - def __init__( - self, - manager: "ActionManager", - max_acl_rules: int, - num_ips: int, - num_ports: int, - num_protocols: int, - **kwargs, - ) -> None: - """Init method for RouterACLAddRuleAction. - - :param manager: Reference to the ActionManager which created this action. - :type manager: ActionManager - :param max_acl_rules: Maximum number of ACL rules that can be added to the router. - :type max_acl_rules: int - :param num_ips: Number of IP addresses in the simulation. - :type num_ips: int - :param num_ports: Number of ports in the simulation. - :type num_ports: int - :param num_protocols: Number of protocols in the simulation. - :type num_protocols: int - """ - super().__init__(manager=manager) - num_permissions = 3 - self.shape: Dict[str, int] = { - "position": max_acl_rules, - "permission": num_permissions, - "source_ip_id": num_ips, - "dest_ip_id": num_ips, - "source_port_id": num_ports, - "dest_port_id": num_ports, - "protocol_id": num_protocols, - } - - def form_request( - self, - target_router: str, - position: int, - permission: int, - source_ip_id: int, - source_wildcard_id: int, - dest_ip_id: int, - dest_wildcard_id: int, - source_port_id: int, - dest_port_id: int, - protocol_id: int, - ) -> List[str]: - """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - # Validate incoming data. - parsed_options = RouterACLAddRuleAction.ACLRuleOptions( - target_router=target_router, - position=position, - permission=permission, - source_ip_id=source_ip_id, - source_wildcard_id=source_wildcard_id, - dest_ip_id=dest_ip_id, - dest_wildcard_id=dest_wildcard_id, - source_port_id=source_port_id, - dest_port_id=dest_port_id, - protocol_id=protocol_id, - ) - if parsed_options.permission == 1: - permission_str = "PERMIT" - elif parsed_options.permission == 2: - permission_str = "DENY" - else: - _LOGGER.warning(f"{self.__class__} received permission {permission}, expected 0 or 1.") - - if parsed_options.protocol_id == 1: - protocol = "ALL" - else: - protocol = self.manager.get_internet_protocol_by_idx(parsed_options.protocol_id - 2) - # subtract 2 to account for UNUSED=0 and ALL=1. - - if parsed_options.source_ip_id == 1: - src_ip = "ALL" - else: - src_ip = self.manager.get_ip_address_by_idx(parsed_options.source_ip_id - 2) - # subtract 2 to account for UNUSED=0, and ALL=1 - - src_wildcard = self.manager.get_wildcard_by_idx(parsed_options.source_wildcard_id) - - if parsed_options.source_port_id == 1: - src_port = "ALL" - else: - src_port = self.manager.get_port_by_idx(parsed_options.source_port_id - 2) - # subtract 2 to account for UNUSED=0, and ALL=1 - - if parsed_options.dest_ip_id == 1: - dst_ip = "ALL" - else: - dst_ip = self.manager.get_ip_address_by_idx(parsed_options.dest_ip_id - 2) - # subtract 2 to account for UNUSED=0, and ALL=1 - dst_wildcard = self.manager.get_wildcard_by_idx(parsed_options.dest_wildcard_id) - - if parsed_options.dest_port_id == 1: - dst_port = "ALL" - else: - dst_port = self.manager.get_port_by_idx(parsed_options.dest_port_id - 2) - # subtract 2 to account for UNUSED=0, and ALL=1 - - return [ - "network", - "node", - target_router, - "acl", - "add_rule", - permission_str, - protocol, - str(src_ip), - src_wildcard, - src_port, - str(dst_ip), - dst_wildcard, - dst_port, - position, - ] - - -class RouterACLRemoveRuleAction(AbstractAction): - """Action which removes a rule from a router's ACL.""" - - def __init__(self, manager: "ActionManager", max_acl_rules: int, **kwargs) -> None: - """Init method for RouterACLRemoveRuleAction. - - :param manager: Reference to the ActionManager which created this action. - :type manager: ActionManager - :param max_acl_rules: Maximum number of ACL rules that can be added to the router. - :type max_acl_rules: int - """ - super().__init__(manager=manager) - self.shape: Dict[str, int] = {"position": max_acl_rules} - - def form_request(self, target_router: str, position: int) -> RequestFormat: - """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - return ["network", "node", target_router, "acl", "remove_rule", position] - - -class FirewallACLAddRuleAction(AbstractAction): - """Action which adds a rule to a firewall port's ACL.""" - - def __init__( - self, - manager: "ActionManager", - max_acl_rules: int, - num_ips: int, - num_ports: int, - num_protocols: int, - **kwargs, - ) -> None: - """Init method for FirewallACLAddRuleAction. - - :param manager: Reference to the ActionManager which created this action. - :type manager: ActionManager - :param max_acl_rules: Maximum number of ACL rules that can be added to the router. - :type max_acl_rules: int - :param num_ips: Number of IP addresses in the simulation. - :type num_ips: int - :param num_ports: Number of ports in the simulation. - :type num_ports: int - :param num_protocols: Number of protocols in the simulation. - :type num_protocols: int - """ - super().__init__(manager=manager) - num_permissions = 3 - self.shape: Dict[str, int] = { - "position": max_acl_rules, - "permission": num_permissions, - "source_ip_id": num_ips, - "dest_ip_id": num_ips, - "source_port_id": num_ports, - "dest_port_id": num_ports, - "protocol_id": num_protocols, - } - - def form_request( - self, - target_firewall_nodename: str, - firewall_port_name: str, - firewall_port_direction: str, - position: int, - permission: int, - source_ip_id: int, - source_wildcard_id: int, - dest_ip_id: int, - dest_wildcard_id: int, - source_port_id: int, - dest_port_id: int, - protocol_id: int, - ) -> List[str]: - """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - if permission == 0: - permission_str = "UNUSED" - return ["do_nothing"] # NOT SUPPORTED, JUST DO NOTHING IF WE COME ACROSS THIS - elif permission == 1: - permission_str = "PERMIT" - elif permission == 2: - permission_str = "DENY" - else: - _LOGGER.warning(f"{self.__class__} received permission {permission}, expected 0 or 1.") - - if protocol_id == 0: - return ["do_nothing"] # NOT SUPPORTED, JUST DO NOTHING IF WE COME ACROSS THIS - - if protocol_id == 1: - protocol = "ALL" - else: - protocol = self.manager.get_internet_protocol_by_idx(protocol_id - 2) - # subtract 2 to account for UNUSED=0 and ALL=1. - - if source_ip_id == 0: - return ["do_nothing"] # invalid formulation - elif source_ip_id == 1: - src_ip = "ALL" - else: - src_ip = self.manager.get_ip_address_by_idx(source_ip_id - 2) - # subtract 2 to account for UNUSED=0, and ALL=1 - - if source_port_id == 0: - return ["do_nothing"] # invalid formulation - elif source_port_id == 1: - src_port = "ALL" - else: - src_port = self.manager.get_port_by_idx(source_port_id - 2) - # subtract 2 to account for UNUSED=0, and ALL=1 - - if dest_ip_id == 0: - return ["do_nothing"] # invalid formulation - elif dest_ip_id == 1: - dst_ip = "ALL" - else: - dst_ip = self.manager.get_ip_address_by_idx(dest_ip_id - 2) - # subtract 2 to account for UNUSED=0, and ALL=1 - - if dest_port_id == 0: - return ["do_nothing"] # invalid formulation - elif dest_port_id == 1: - dst_port = "ALL" - else: - dst_port = self.manager.get_port_by_idx(dest_port_id - 2) - # subtract 2 to account for UNUSED=0, and ALL=1 - src_wildcard = self.manager.get_wildcard_by_idx(source_wildcard_id) - dst_wildcard = self.manager.get_wildcard_by_idx(dest_wildcard_id) - - return [ - "network", - "node", - target_firewall_nodename, - firewall_port_name, - firewall_port_direction, - "acl", - "add_rule", - permission_str, - protocol, - str(src_ip), - src_wildcard, - src_port, - str(dst_ip), - dst_wildcard, - dst_port, - position, - ] - - -class FirewallACLRemoveRuleAction(AbstractAction): - """Action which removes a rule from a firewall port's ACL.""" - - def __init__(self, manager: "ActionManager", max_acl_rules: int, **kwargs) -> None: - """Init method for RouterACLRemoveRuleAction. - - :param manager: Reference to the ActionManager which created this action. - :type manager: ActionManager - :param max_acl_rules: Maximum number of ACL rules that can be added to the router. - :type max_acl_rules: int - """ - super().__init__(manager=manager) - self.shape: Dict[str, int] = {"position": max_acl_rules} - - def form_request( - self, target_firewall_nodename: str, firewall_port_name: str, firewall_port_direction: str, position: int - ) -> List[str]: - """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - return [ - "network", - "node", - target_firewall_nodename, - firewall_port_name, - firewall_port_direction, - "acl", - "remove_rule", - position, - ] - - -class HostNICAbstractAction(AbstractAction): - """ - Abstract base class for NIC actions. - - Any action which applies to a NIC and uses node_id and nic_id as its only two parameters can inherit from this base - class. - """ - - def __init__(self, manager: "ActionManager", num_nodes: int, max_nics_per_node: int, **kwargs) -> None: - """Init method for HostNICAbstractAction. - - :param manager: Reference to the ActionManager which created this action. - :type manager: ActionManager - :param num_nodes: Number of nodes in the simulation. - :type num_nodes: int - :param max_nics_per_node: Maximum number of NICs per node. - :type max_nics_per_node: int - """ - super().__init__(manager=manager) - self.shape: Dict[str, int] = {"node_id": num_nodes, "nic_id": max_nics_per_node} - self.verb: str # define but don't initialise: defends against children classes not defining this - - def form_request(self, node_id: int, nic_id: int) -> RequestFormat: - """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - node_name = self.manager.get_node_name_by_idx(node_idx=node_id) - nic_num = self.manager.get_nic_num_by_idx(node_idx=node_id, nic_idx=nic_id) - if node_name is None or nic_num is None: - return ["do_nothing"] - return ["network", "node", node_name, "network_interface", nic_num, self.verb] - - -class HostNICEnableAction(HostNICAbstractAction): - """Action which enables a NIC.""" - - def __init__(self, manager: "ActionManager", num_nodes: int, max_nics_per_node: int, **kwargs) -> None: - super().__init__(manager=manager, num_nodes=num_nodes, max_nics_per_node=max_nics_per_node, **kwargs) - self.verb: str = "enable" - - -class HostNICDisableAction(HostNICAbstractAction): - """Action which disables a NIC.""" - - def __init__(self, manager: "ActionManager", num_nodes: int, max_nics_per_node: int, **kwargs) -> None: - super().__init__(manager=manager, num_nodes=num_nodes, max_nics_per_node=max_nics_per_node, **kwargs) - self.verb: str = "disable" - - -class NetworkPortEnableAction(AbstractAction): - """Action which enables are port on a router or a firewall.""" - - def __init__(self, manager: "ActionManager", max_nics_per_node: int, **kwargs) -> None: - """Init method for NetworkPortEnableAction. - - :param max_nics_per_node: Maximum number of NICs per node. - :type max_nics_per_node: int - """ - super().__init__(manager=manager) - self.shape: Dict[str, int] = {"port_id": max_nics_per_node} - - def form_request(self, target_nodename: str, port_id: int) -> RequestFormat: - """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - if target_nodename is None or port_id is None: - return ["do_nothing"] - return ["network", "node", target_nodename, "network_interface", port_id, "enable"] - - -class NetworkPortDisableAction(AbstractAction): - """Action which disables are port on a router or a firewall.""" - - def __init__(self, manager: "ActionManager", max_nics_per_node: int, **kwargs) -> None: - """Init method for NetworkPortDisableAction. - - :param max_nics_per_node: Maximum number of NICs per node. - :type max_nics_per_node: int - """ - super().__init__(manager=manager) - self.shape: Dict[str, int] = {"port_id": max_nics_per_node} - - def form_request(self, target_nodename: str, port_id: int) -> RequestFormat: - """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - if target_nodename is None or port_id is None: - return ["do_nothing"] - return ["network", "node", target_nodename, "network_interface", port_id, "disable"] - - -class NodeNMAPPingScanAction(AbstractAction): - """Action which performs an NMAP ping scan.""" - - def __init__(self, manager: "ActionManager", **kwargs) -> None: - super().__init__(manager=manager) - - def form_request( - self, source_node: str, target_ip_address: Union[str, List[str]], show: Optional[bool] = False - ) -> List[str]: # noqa - """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - return [ - "network", - "node", - source_node, - "application", - "NMAP", - "ping_scan", - {"target_ip_address": target_ip_address, "show": show}, - ] - - -class NodeNMAPPortScanAction(AbstractAction): - """Action which performs an NMAP port scan.""" - - def __init__(self, manager: "ActionManager", **kwargs) -> None: - super().__init__(manager=manager) - - def form_request( - self, - source_node: str, - target_ip_address: Union[str, List[str]], - target_protocol: Optional[Union[str, List[str]]] = None, - target_port: Optional[Union[str, List[str]]] = None, - show: Optional[bool] = False, - ) -> List[str]: # noqa - """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - return [ - "network", - "node", - source_node, - "application", - "NMAP", - "port_scan", - { - "target_ip_address": target_ip_address, - "target_port": target_port, - "target_protocol": target_protocol, - "show": show, - }, - ] - - -class NodeNetworkServiceReconAction(AbstractAction): - """Action which performs an NMAP network service recon (ping scan followed by port scan).""" - - def __init__(self, manager: "ActionManager", **kwargs) -> None: - super().__init__(manager=manager) - - def form_request( - self, - source_node: str, - target_ip_address: Union[str, List[str]], - target_protocol: Optional[Union[str, List[str]]] = None, - target_port: Optional[Union[str, List[str]]] = None, - show: Optional[bool] = False, - ) -> List[str]: # noqa - """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - return [ - "network", - "node", - source_node, - "application", - "NMAP", - "network_service_recon", - { - "target_ip_address": target_ip_address, - "target_port": target_port, - "target_protocol": target_protocol, - "show": show, - }, - ] - - -class ConfigureC2BeaconAction(AbstractAction): - """Action which configures a C2 Beacon based on the parameters given.""" - - class _Opts(BaseModel): - """Schema for options that can be passed to this action.""" - - c2_server_ip_address: str - keep_alive_frequency: int = Field(default=5, ge=1) - masquerade_protocol: str = Field(default="TCP") - masquerade_port: str = Field(default="HTTP") - - @field_validator( - "c2_server_ip_address", - "keep_alive_frequency", - "masquerade_protocol", - "masquerade_port", - mode="before", - ) - @classmethod - def not_none(cls, v: str, info: ValidationInfo) -> int: - """If None is passed, use the default value instead.""" - if v is None: - return cls.model_fields[info.field_name].default - return v - - def __init__(self, manager: "ActionManager", **kwargs) -> None: - super().__init__(manager=manager) - - def form_request(self, node_id: int, config: Dict) -> RequestFormat: - """Return the action formatted as a request that can be ingested by the simulation.""" - node_name = self.manager.get_node_name_by_idx(node_id) - if node_name is None: - return ["do_nothing"] - config = ConfigureC2BeaconAction._Opts( - c2_server_ip_address=config["c2_server_ip_address"], - keep_alive_frequency=config["keep_alive_frequency"], - masquerade_port=config["masquerade_port"], - masquerade_protocol=config["masquerade_protocol"], - ) - - ConfigureC2BeaconAction._Opts.model_validate(config) # check that options adhere to schema - - return ["network", "node", node_name, "application", "C2Beacon", "configure", config.__dict__] - - -class NodeAccountsChangePasswordAction(AbstractAction): - """Action which changes the password for a user.""" - - def __init__(self, manager: "ActionManager", **kwargs) -> None: - super().__init__(manager=manager) - - def form_request(self, node_id: str, username: str, current_password: str, new_password: str) -> RequestFormat: - """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - node_name = self.manager.get_node_name_by_idx(node_id) - return [ - "network", - "node", - node_name, - "service", - "UserManager", - "change_password", - username, - current_password, - new_password, - ] - - -class NodeSessionsRemoteLoginAction(AbstractAction): - """Action which performs a remote session login.""" - - def __init__(self, manager: "ActionManager", **kwargs) -> None: - super().__init__(manager=manager) - - def form_request(self, node_id: str, username: str, password: str, remote_ip: str) -> RequestFormat: - """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - node_name = self.manager.get_node_name_by_idx(node_id) - return [ - "network", - "node", - node_name, - "service", - "Terminal", - "ssh_to_remote", - username, - password, - remote_ip, - ] - - -class NodeSessionsRemoteLogoutAction(AbstractAction): - """Action which performs a remote session logout.""" - - def __init__(self, manager: "ActionManager", **kwargs) -> None: - super().__init__(manager=manager) - - def form_request(self, node_id: str, remote_ip: str) -> RequestFormat: - """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - node_name = self.manager.get_node_name_by_idx(node_id) - return ["network", "node", node_name, "service", "Terminal", "remote_logoff", remote_ip] - - -class RansomwareConfigureC2ServerAction(AbstractAction): - """Action which sends a command from the C2 Server to the C2 Beacon which configures a local RansomwareScript.""" - - def __init__(self, manager: "ActionManager", **kwargs) -> None: - super().__init__(manager=manager) - - def form_request(self, node_id: int, config: Dict) -> RequestFormat: - """Return the action formatted as a request that can be ingested by the simulation.""" - node_name = self.manager.get_node_name_by_idx(node_id) - if node_name is None: - return ["do_nothing"] - # Using the ransomware scripts model to validate. - ConfigureRansomwareScriptAction._Opts.model_validate(config) # check that options adhere to schema - return ["network", "node", node_name, "application", "C2Server", "ransomware_configure", config] - - -class RansomwareLaunchC2ServerAction(AbstractAction): - """Action which causes the C2 Server to send a command to the C2 Beacon to launch the RansomwareScript.""" - - def __init__(self, manager: "ActionManager", **kwargs) -> None: - super().__init__(manager=manager) - - def form_request(self, node_id: int) -> RequestFormat: - """Return the action formatted as a request that can be ingested by the simulation.""" - node_name = self.manager.get_node_name_by_idx(node_id) - if node_name is None: - return ["do_nothing"] - # This action currently doesn't require any further configuration options. - return ["network", "node", node_name, "application", "C2Server", "ransomware_launch"] - - -class ExfiltrationC2ServerAction(AbstractAction): - """Action which exfiltrates a target file from a certain node onto the C2 beacon and then the C2 Server.""" - - class _Opts(BaseModel): - """Schema for options that can be passed to this action.""" - - username: Optional[str] - password: Optional[str] - target_ip_address: str - target_file_name: str - target_folder_name: str - exfiltration_folder_name: Optional[str] - - def __init__(self, manager: "ActionManager", **kwargs) -> None: - super().__init__(manager=manager) - - def form_request( - self, - node_id: int, - account: dict, - target_ip_address: str, - target_file_name: str, - target_folder_name: str, - exfiltration_folder_name: Optional[str], - ) -> RequestFormat: - """Return the action formatted as a request that can be ingested by the simulation.""" - node_name = self.manager.get_node_name_by_idx(node_id) - if node_name is None: - return ["do_nothing"] - - command_model = { - "target_file_name": target_file_name, - "target_folder_name": target_folder_name, - "exfiltration_folder_name": exfiltration_folder_name, - "target_ip_address": target_ip_address, - "username": account["username"], - "password": account["password"], - } - ExfiltrationC2ServerAction._Opts.model_validate(command_model) - return ["network", "node", node_name, "application", "C2Server", "exfiltrate", command_model] - - -class NodeSendRemoteCommandAction(AbstractAction): - """Action which sends a terminal command to a remote node via SSH.""" - - def __init__(self, manager: "ActionManager", **kwargs) -> None: - super().__init__(manager=manager) - - def form_request(self, node_id: int, remote_ip: str, command: RequestFormat) -> RequestFormat: - """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" - node_name = self.manager.get_node_name_by_idx(node_id) - return [ - "network", - "node", - node_name, - "service", - "Terminal", - "send_remote_command", - remote_ip, - {"command": command}, - ] - - -class TerminalC2ServerAction(AbstractAction): - """Action which causes the C2 Server to send a command to the C2 Beacon to execute the terminal command passed.""" - - class _Opts(BaseModel): - """Schema for options that can be passed to this action.""" - - commands: Union[List[RequestFormat], RequestFormat] - ip_address: Optional[str] - username: Optional[str] - password: Optional[str] - - def __init__(self, manager: "ActionManager", **kwargs) -> None: - super().__init__(manager=manager) - - def form_request(self, node_id: int, commands: List, ip_address: Optional[str], account: dict) -> RequestFormat: - """Return the action formatted as a request that can be ingested by the simulation.""" - node_name = self.manager.get_node_name_by_idx(node_id) - if node_name is None: - return ["do_nothing"] - - command_model = { - "commands": commands, - "ip_address": ip_address, - "username": account["username"], - "password": account["password"], - } - - TerminalC2ServerAction._Opts.model_validate(command_model) - return ["network", "node", node_name, "application", "C2Server", "terminal_command", command_model] - - -class RansomwareLaunchC2ServerAction(AbstractAction): - """Action which causes the C2 Server to send a command to the C2 Beacon to launch the RansomwareScript.""" - - def __init__(self, manager: "ActionManager", **kwargs) -> None: - super().__init__(manager=manager) - - def form_request(self, node_id: int) -> RequestFormat: - """Return the action formatted as a request that can be ingested by the simulation.""" - node_name = self.manager.get_node_name_by_idx(node_id) - if node_name is None: - return ["do_nothing"] - # This action currently doesn't require any further configuration options. - return ["network", "node", node_name, "application", "C2Server", "ransomware_launch"] diff --git a/src/primaite/game/agent/actions/abstract.py b/src/primaite/game/agent/actions/abstract.py index 15c9b4cb..c97d2bc0 100644 --- a/src/primaite/game/agent/actions/abstract.py +++ b/src/primaite/game/agent/actions/abstract.py @@ -9,7 +9,7 @@ from pydantic import BaseModel, ConfigDict from primaite.interface.request import RequestFormat -class AbstractAction(BaseModel): +class AbstractAction(BaseModel, ABC): """Base class for actions.""" config: "AbstractAction.ConfigSchema" diff --git a/src/primaite/game/agent/actions/acl.py b/src/primaite/game/agent/actions/acl.py index 6fefeeda..6022f697 100644 --- a/src/primaite/game/agent/actions/acl.py +++ b/src/primaite/game/agent/actions/acl.py @@ -1,7 +1,7 @@ # © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK from __future__ import annotations -from ipaddress import IPv4Address +from abc import ABC from typing import List from pydantic import field_validator @@ -9,7 +9,7 @@ from pydantic import field_validator from primaite.game.agent.actions.manager import AbstractAction from primaite.interface.request import RequestFormat from primaite.utils.validation.ip_protocol import protocol_validator -from primaite.utils.validation.ipv4_address import ipv4_validator +from primaite.utils.validation.ipv4_address import ipv4_validator, IPV4Address from primaite.utils.validation.port import port_validator __all__ = ( @@ -20,7 +20,7 @@ __all__ = ( ) -class ACLAddRuleAbstractAction(AbstractAction, identifier="acl_add_rule_abstract_action"): +class ACLAddRuleAbstractAction(AbstractAction, ABC): """Base abstract class for ACL add rule actions.""" config: ConfigSchema = "ACLAddRuleAbstractAction.ConfigSchema" @@ -28,11 +28,11 @@ class ACLAddRuleAbstractAction(AbstractAction, identifier="acl_add_rule_abstract class ConfigSchema(AbstractAction.ConfigSchema): """Configuration Schema base for ACL add rule abstract actions.""" - src_ip: IPv4Address + src_ip: IPV4Address protocol_name: str permission: str position: int - dst_ip: IPv4Address + dst_ip: IPV4Address src_port: int dst_port: int src_wildcard: int diff --git a/src/primaite/game/agent/actions/application.py b/src/primaite/game/agent/actions/application.py index 96609f93..223effc4 100644 --- a/src/primaite/game/agent/actions/application.py +++ b/src/primaite/game/agent/actions/application.py @@ -1,4 +1,5 @@ # © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +from abc import ABC from typing import ClassVar from primaite.game.agent.actions.abstract import AbstractAction @@ -14,7 +15,7 @@ __all__ = ( ) -class NodeApplicationAbstractAction(AbstractAction, identifier="node_application_abstract_action"): +class NodeApplicationAbstractAction(AbstractAction, ABC): """ Base class for application actions. diff --git a/src/primaite/game/agent/actions/file.py b/src/primaite/game/agent/actions/file.py index e5ca1c46..bcfb27ac 100644 --- a/src/primaite/game/agent/actions/file.py +++ b/src/primaite/game/agent/actions/file.py @@ -1,4 +1,5 @@ # © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +from abc import ABC from typing import ClassVar from primaite.game.agent.actions.manager import AbstractAction @@ -16,7 +17,7 @@ __all__ = ( ) -class NodeFileAbstractAction(AbstractAction, identifier="node_file_abstract_action"): +class NodeFileAbstractAction(AbstractAction, ABC): """Abstract base class for file actions. Any action which applies to a file and uses node_name, folder_name, and file_name as its diff --git a/src/primaite/game/agent/actions/folder.py b/src/primaite/game/agent/actions/folder.py index d1fd5ef1..7fb90f75 100644 --- a/src/primaite/game/agent/actions/folder.py +++ b/src/primaite/game/agent/actions/folder.py @@ -1,4 +1,5 @@ # © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +from abc import ABC from typing import ClassVar from primaite.game.agent.actions.manager import AbstractAction @@ -13,7 +14,7 @@ __all__ = ( ) -class NodeFolderAbstractAction(AbstractAction, identifier="node_folder_abstract"): +class NodeFolderAbstractAction(AbstractAction, ABC): """ Base class for folder actions. diff --git a/src/primaite/game/agent/actions/host_nic.py b/src/primaite/game/agent/actions/host_nic.py index 7b290103..9e3cb71a 100644 --- a/src/primaite/game/agent/actions/host_nic.py +++ b/src/primaite/game/agent/actions/host_nic.py @@ -1,4 +1,5 @@ # © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK +from abc import ABC from typing import ClassVar from primaite.game.agent.actions.manager import AbstractAction @@ -7,7 +8,7 @@ from primaite.interface.request import RequestFormat __all__ = ("HostNICEnableAction", "HostNICDisableAction") -class HostNICAbstractAction(AbstractAction, identifier="host_nic_abstract"): +class HostNICAbstractAction(AbstractAction, ABC): """ Abstract base class for NIC actions. diff --git a/src/primaite/game/agent/actions/manager.py b/src/primaite/game/agent/actions/manager.py index 625d8cec..c3e14379 100644 --- a/src/primaite/game/agent/actions/manager.py +++ b/src/primaite/game/agent/actions/manager.py @@ -127,9 +127,6 @@ class ActionManager: :return: The constructed ActionManager. :rtype: ActionManager """ - if "ip_list" not in cfg["options"]: - cfg["options"]["ip_list"] = [] - obj = cls( actions=cfg["action_list"], **cfg["options"], diff --git a/src/primaite/simulator/system/core/software_manager.py b/src/primaite/simulator/system/core/software_manager.py index 5e63f2ec..f0ee6f7c 100644 --- a/src/primaite/simulator/system/core/software_manager.py +++ b/src/primaite/simulator/system/core/software_manager.py @@ -207,10 +207,6 @@ class SoftwareManager: :param session_id: The Session ID from which the payload originates. Optional. :return: True if the payload was successfully sent, False otherwise. """ - print(payload) - print(dest_ip_address) - print(src_port) - print(session_id) return self.session_manager.receive_payload_from_software_manager( payload=payload, dst_ip_address=dest_ip_address, diff --git a/src/primaite/utils/validation/ipv4_address.py b/src/primaite/utils/validation/ipv4_address.py index c385ed1e..b2b8b72e 100644 --- a/src/primaite/utils/validation/ipv4_address.py +++ b/src/primaite/utils/validation/ipv4_address.py @@ -31,7 +31,7 @@ def ipv4_validator(v: Any) -> IPv4Address: IPV4Address: Final[Annotated] = Annotated[IPv4Address, BeforeValidator(ipv4_validator)] """ -IPv4Address with with IPv4Address with with pre-validation and auto-conversion from str using ipv4_validator.. +IPv4Address with pre-validation and auto-conversion from str using ipv4_validator.. This type is essentially an IPv4Address from the standard library's ipaddress module, but with added validation logic. If you use this custom type, the ipv4_validator function diff --git a/tests/unit_tests/_primaite/_game/_agent/test_actions.py b/tests/unit_tests/_primaite/_game/_agent/test_actions.py index 9021b8af..1699798a 100644 --- a/tests/unit_tests/_primaite/_game/_agent/test_actions.py +++ b/tests/unit_tests/_primaite/_game/_agent/test_actions.py @@ -5,7 +5,7 @@ import pytest from primaite.game.agent.actions import ( ActionManager, - do_nothingAction, + DoNothingAction, NodeServiceDisableAction, NodeServiceEnableAction, NodeServicePauseAction, diff --git a/tests/unit_tests/_primaite/_game/_agent/test_sticky_rewards.py b/tests/unit_tests/_primaite/_game/_agent/test_sticky_rewards.py index 916b35c0..91d5c607 100644 --- a/tests/unit_tests/_primaite/_game/_agent/test_sticky_rewards.py +++ b/tests/unit_tests/_primaite/_game/_agent/test_sticky_rewards.py @@ -81,7 +81,7 @@ class TestWebpageUnavailabilitySticky: reward = WebpageUnavailablePenalty(config=schema) # no response codes yet, reward is 0 - action, params, request = "DO_NOTHING", {}, ["do_nothing"] + action, params, request = "do_nothing", {}, ["do_nothing"] response = RequestResponse(status="success", data={}) browser_history = [] state = {"network": {"nodes": {"computer": {"applications": {"WebBrowser": {"history": browser_history}}}}}} From 3528b712f12c98b67020a230ef62ff3edfa656b3 Mon Sep 17 00:00:00 2001 From: Charlie Crane Date: Mon, 13 Jan 2025 16:35:08 +0000 Subject: [PATCH 57/58] #2912 - Correct instances of verb: str to verb: ClassVar[str] where the parent class uses ClassVar[str] --- src/primaite/game/agent/actions/file.py | 16 ++++++++-------- src/primaite/game/agent/actions/folder.py | 10 +++++----- src/primaite/game/agent/actions/host_nic.py | 4 ++-- src/primaite/game/agent/actions/network.py | 4 ++-- src/primaite/game/agent/actions/node.py | 8 ++++---- src/primaite/game/agent/actions/service.py | 18 +++++++++--------- 6 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/primaite/game/agent/actions/file.py b/src/primaite/game/agent/actions/file.py index bcfb27ac..ed666773 100644 --- a/src/primaite/game/agent/actions/file.py +++ b/src/primaite/game/agent/actions/file.py @@ -60,7 +60,7 @@ class NodeFileCreateAction(NodeFileAbstractAction, identifier="node_file_create" class ConfigSchema(NodeFileAbstractAction.ConfigSchema): """Configuration schema for NodeFileCreateAction.""" - verb: str = "create" + verb: ClassVar[str] = "create" force: bool = False @classmethod @@ -89,7 +89,7 @@ class NodeFileScanAction(NodeFileAbstractAction, identifier="node_file_scan"): class ConfigSchema(NodeFileAbstractAction.ConfigSchema): """Configuration schema for NodeFileScanAction.""" - verb: str = "scan" + verb: ClassVar[str] = "scan" class NodeFileDeleteAction(NodeFileAbstractAction, identifier="node_file_delete"): @@ -100,7 +100,7 @@ class NodeFileDeleteAction(NodeFileAbstractAction, identifier="node_file_delete" class ConfigSchema(NodeFileAbstractAction.ConfigSchema): """Configuration schema for NodeFileDeleteAction.""" - verb: str = "delete" + verb: ClassVar[str] = "delete" @classmethod def form_request(cls, config: ConfigSchema) -> RequestFormat: @@ -127,7 +127,7 @@ class NodeFileRestoreAction(NodeFileAbstractAction, identifier="node_file_restor class ConfigSchema(NodeFileAbstractAction.ConfigSchema): """Configuration schema for NodeFileRestoreAction.""" - verb: str = "restore" + verb: ClassVar[str] = "restore" class NodeFileCorruptAction(NodeFileAbstractAction, identifier="node_file_corrupt"): @@ -138,7 +138,7 @@ class NodeFileCorruptAction(NodeFileAbstractAction, identifier="node_file_corrup class ConfigSchema(NodeFileAbstractAction.ConfigSchema): """Configuration schema for NodeFileCorruptAction.""" - verb: str = "corrupt" + verb: ClassVar[str] = "corrupt" class NodeFileAccessAction(NodeFileAbstractAction, identifier="node_file_access"): @@ -149,7 +149,7 @@ class NodeFileAccessAction(NodeFileAbstractAction, identifier="node_file_access" class ConfigSchema(NodeFileAbstractAction.ConfigSchema): """Configuration schema for NodeFileAccessAction.""" - verb: str = "access" + verb: ClassVar[str] = "access" @classmethod def form_request(cls, config: ConfigSchema) -> RequestFormat: @@ -175,7 +175,7 @@ class NodeFileCheckhashAction(NodeFileAbstractAction, identifier="node_file_chec class ConfigSchema(NodeFileAbstractAction.ConfigSchema): """Configuration schema for NodeFileCheckhashAction.""" - verb: str = "checkhash" + verb: ClassVar[str] = "checkhash" class NodeFileRepairAction(NodeFileAbstractAction, identifier="node_file_repair"): @@ -186,4 +186,4 @@ class NodeFileRepairAction(NodeFileAbstractAction, identifier="node_file_repair" class ConfigSchema(NodeFileAbstractAction.ConfigSchema): """Configuration Schema for NodeFileRepairAction.""" - verb: str = "repair" + verb: ClassVar[str] = "repair" diff --git a/src/primaite/game/agent/actions/folder.py b/src/primaite/game/agent/actions/folder.py index 7fb90f75..3e1136ac 100644 --- a/src/primaite/game/agent/actions/folder.py +++ b/src/primaite/game/agent/actions/folder.py @@ -55,7 +55,7 @@ class NodeFolderScanAction(NodeFolderAbstractAction, identifier="node_folder_sca class ConfigSchema(NodeFolderAbstractAction.ConfigSchema): """Configuration schema for NodeFolderScanAction.""" - verb: str = "scan" + verb: ClassVar[str] = "scan" class NodeFolderCheckhashAction(NodeFolderAbstractAction, identifier="node_folder_checkhash"): @@ -66,7 +66,7 @@ class NodeFolderCheckhashAction(NodeFolderAbstractAction, identifier="node_folde class ConfigSchema(NodeFolderAbstractAction.ConfigSchema): """Configuration schema for NodeFolderCheckhashAction.""" - verb: str = "checkhash" + verb: ClassVar[str] = "checkhash" class NodeFolderRepairAction(NodeFolderAbstractAction, identifier="node_folder_repair"): @@ -77,7 +77,7 @@ class NodeFolderRepairAction(NodeFolderAbstractAction, identifier="node_folder_r class ConfigSchema(NodeFolderAbstractAction.ConfigSchema): """Configuration schema for NodeFolderRepairAction.""" - verb: str = "repair" + verb: ClassVar[str] = "repair" class NodeFolderRestoreAction(NodeFolderAbstractAction, identifier="node_folder_restore"): @@ -88,7 +88,7 @@ class NodeFolderRestoreAction(NodeFolderAbstractAction, identifier="node_folder_ class ConfigSchema(NodeFolderAbstractAction.ConfigSchema): """Configuration schema for NodeFolderRestoreAction.""" - verb: str = "restore" + verb: ClassVar[str] = "restore" class NodeFolderCreateAction(NodeFolderAbstractAction, identifier="node_folder_create"): @@ -99,7 +99,7 @@ class NodeFolderCreateAction(NodeFolderAbstractAction, identifier="node_folder_c class ConfigSchema(NodeFolderAbstractAction.ConfigSchema): """Configuration schema for NodeFolderCreateAction.""" - verb: str = "create" + verb: ClassVar[str] = "create" @classmethod def form_request(cls, config: ConfigSchema) -> RequestFormat: diff --git a/src/primaite/game/agent/actions/host_nic.py b/src/primaite/game/agent/actions/host_nic.py index 9e3cb71a..b9206b9c 100644 --- a/src/primaite/game/agent/actions/host_nic.py +++ b/src/primaite/game/agent/actions/host_nic.py @@ -48,7 +48,7 @@ class HostNICEnableAction(HostNICAbstractAction, identifier="host_nic_enable"): class ConfigSchema(HostNICAbstractAction.ConfigSchema): """Configuration schema for HostNICEnableAction.""" - verb: str = "enable" + verb: ClassVar[str] = "enable" class HostNICDisableAction(HostNICAbstractAction, identifier="host_nic_disable"): @@ -59,4 +59,4 @@ class HostNICDisableAction(HostNICAbstractAction, identifier="host_nic_disable") class ConfigSchema(HostNICAbstractAction.ConfigSchema): """Configuration schema for HostNICDisableAction.""" - verb: str = "disable" + verb: ClassVar[str] = "disable" diff --git a/src/primaite/game/agent/actions/network.py b/src/primaite/game/agent/actions/network.py index fa1c4451..7f1e069a 100644 --- a/src/primaite/game/agent/actions/network.py +++ b/src/primaite/game/agent/actions/network.py @@ -43,7 +43,7 @@ class NetworkPortEnableAction(NetworkPortAbstractAction, identifier="network_por class ConfigSchema(NetworkPortAbstractAction.ConfigSchema): """Configuration schema for NetworkPortEnableAction.""" - verb: str = "enable" + verb: ClassVar[str] = "enable" class NetworkPortDisableAction(NetworkPortAbstractAction, identifier="network_port_disable"): @@ -54,4 +54,4 @@ class NetworkPortDisableAction(NetworkPortAbstractAction, identifier="network_po class ConfigSchema(NetworkPortAbstractAction.ConfigSchema): """Configuration schema for NetworkPortDisableAction.""" - verb: str = "disable" + verb: ClassVar[str] = "disable" diff --git a/src/primaite/game/agent/actions/node.py b/src/primaite/game/agent/actions/node.py index c6b74f2e..4a7f725e 100644 --- a/src/primaite/game/agent/actions/node.py +++ b/src/primaite/game/agent/actions/node.py @@ -46,7 +46,7 @@ class NodeOSScanAction(NodeAbstractAction, identifier="node_os_scan"): class ConfigSchema(NodeAbstractAction.ConfigSchema): """Configuration schema for NodeOSScanAction.""" - verb: str = "scan" + verb: ClassVar[str] = "scan" class NodeShutdownAction(NodeAbstractAction, identifier="node_shutdown"): @@ -57,7 +57,7 @@ class NodeShutdownAction(NodeAbstractAction, identifier="node_shutdown"): class ConfigSchema(NodeAbstractAction.ConfigSchema): """Configuration schema for NodeShutdownAction.""" - verb: str = "shutdown" + verb: ClassVar[str] = "shutdown" class NodeStartupAction(NodeAbstractAction, identifier="node_startup"): @@ -68,7 +68,7 @@ class NodeStartupAction(NodeAbstractAction, identifier="node_startup"): class ConfigSchema(NodeAbstractAction.ConfigSchema): """Configuration schema for NodeStartupAction.""" - verb: str = "startup" + verb: ClassVar[str] = "startup" class NodeResetAction(NodeAbstractAction, identifier="node_reset"): @@ -79,7 +79,7 @@ class NodeResetAction(NodeAbstractAction, identifier="node_reset"): class ConfigSchema(NodeAbstractAction.ConfigSchema): """Configuration schema for NodeResetAction.""" - verb: str = "reset" + verb: ClassVar[str] = "reset" class NodeNMAPAbstractAction(AbstractAction, identifier="node_nmap_abstract_action"): diff --git a/src/primaite/game/agent/actions/service.py b/src/primaite/game/agent/actions/service.py index fa47ffb1..4a483f28 100644 --- a/src/primaite/game/agent/actions/service.py +++ b/src/primaite/game/agent/actions/service.py @@ -44,7 +44,7 @@ class NodeServiceScanAction(NodeServiceAbstractAction, identifier="node_service_ class ConfigSchema(NodeServiceAbstractAction.ConfigSchema): """Configuration Schema for NodeServiceScanAction.""" - verb: str = "scan" + verb: ClassVar[str] = "scan" class NodeServiceStopAction(NodeServiceAbstractAction, identifier="node_service_stop"): @@ -55,7 +55,7 @@ class NodeServiceStopAction(NodeServiceAbstractAction, identifier="node_service_ class ConfigSchema(NodeServiceAbstractAction.ConfigSchema): """Configuration Schema for NodeServiceStopAction.""" - verb: str = "stop" + verb: ClassVar[str] = "stop" class NodeServiceStartAction(NodeServiceAbstractAction, identifier="node_service_start"): @@ -66,7 +66,7 @@ class NodeServiceStartAction(NodeServiceAbstractAction, identifier="node_service class ConfigSchema(NodeServiceAbstractAction.ConfigSchema): """Configuration Schema for NodeServiceStartAction.""" - verb: str = "start" + verb: ClassVar[str] = "start" class NodeServicePauseAction(NodeServiceAbstractAction, identifier="node_service_pause"): @@ -77,7 +77,7 @@ class NodeServicePauseAction(NodeServiceAbstractAction, identifier="node_service class ConfigSchema(NodeServiceAbstractAction.ConfigSchema): """Configuration Schema for NodeServicePauseAction.""" - verb: str = "pause" + verb: ClassVar[str] = "pause" class NodeServiceResumeAction(NodeServiceAbstractAction, identifier="node_service_resume"): @@ -88,7 +88,7 @@ class NodeServiceResumeAction(NodeServiceAbstractAction, identifier="node_servic class ConfigSchema(NodeServiceAbstractAction.ConfigSchema): """Configuration Schema for NodeServiceResumeAction.""" - verb: str = "resume" + verb: ClassVar[str] = "resume" class NodeServiceRestartAction(NodeServiceAbstractAction, identifier="node_service_restart"): @@ -99,7 +99,7 @@ class NodeServiceRestartAction(NodeServiceAbstractAction, identifier="node_servi class ConfigSchema(NodeServiceAbstractAction.ConfigSchema): """Configuration Schema for NodeServiceRestartAction.""" - verb: str = "restart" + verb: ClassVar[str] = "restart" class NodeServiceDisableAction(NodeServiceAbstractAction, identifier="node_service_disable"): @@ -110,7 +110,7 @@ class NodeServiceDisableAction(NodeServiceAbstractAction, identifier="node_servi class ConfigSchema(NodeServiceAbstractAction.ConfigSchema): """Configuration Schema for NodeServiceDisableAction.""" - verb: str = "disable" + verb: ClassVar[str] = "disable" class NodeServiceEnableAction(NodeServiceAbstractAction, identifier="node_service_enable"): @@ -121,7 +121,7 @@ class NodeServiceEnableAction(NodeServiceAbstractAction, identifier="node_servic class ConfigSchema(NodeServiceAbstractAction.ConfigSchema): """Configuration Schema for NodeServiceEnableAction.""" - verb: str = "enable" + verb: ClassVar[str] = "enable" class NodeServiceFixAction(NodeServiceAbstractAction, identifier="node_service_fix"): @@ -132,4 +132,4 @@ class NodeServiceFixAction(NodeServiceAbstractAction, identifier="node_service_f class ConfigSchema(NodeServiceAbstractAction.ConfigSchema): """Configuration Schema for NodeServiceFixAction.""" - verb: str = "fix" + verb: ClassVar[str] = "fix" From 1ac562ebc9c2de6f779a736f1bb085603a117d20 Mon Sep 17 00:00:00 2001 From: Charlie Crane Date: Mon, 13 Jan 2025 17:09:03 +0000 Subject: [PATCH 58/58] #2912 - Initial layout of extensible_actions documentation page --- .../how_to_guides/extensible_actions.rst | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/docs/source/how_to_guides/extensible_actions.rst b/docs/source/how_to_guides/extensible_actions.rst index f2e053aa..0064a3a7 100644 --- a/docs/source/how_to_guides/extensible_actions.rst +++ b/docs/source/how_to_guides/extensible_actions.rst @@ -1,3 +1,67 @@ .. only:: comment © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK + + +Extensible Actions +****************** + + +Changes to Actions class Structure. +=================================== + +Actions within PrimAITE have been updated to inherit from a base class, AbstractAction, standardising their format and allowing for easier creation of custom actions. Actions now use a ``ConfigSchema`` to define the possible configuration variables, and use pydantic to enforce correct parameters are passed through. + + +Developing Custom Actions. +========================== + +Custom actions within PrimAITE must be a sub-class of `AbstractAction`, and contain 3 key items: + +#. ConfigSchema class + +#. Unique Identifier + +#. `from_request` method. + + +ConfigSchema +############ + +The ConfigSchema sub-class of the action must contain all `configurable` variables within the action, that would be specified within the environments configuration YAML file. + + +Unique Identifier +################# + +When declaring a custom class, it must have a unique identifier string, that allows PrimAITE to generate the correct action when needed. + +.. code:: Python + + class CreateDirectoryAction(AbstractAction, identifier="node_folder_create") + + config: CreateDirectoryAction.ConfigSchema + + class ConfigSchema(AbstractAction.ConfigSchema): + + verb: ClassVar[str] = "create" + node_name: str + directory_name: str + + def form_request(cls, config: ConfigSchema) -> RequestFormat: + return ["network", + "node", + config.node_name, + "file_system", + config.verb, + "folder", + config.directory_name, + ] + +The above action would fail pydantic validation as the identifier "node_folder_create" is already used by the `NodeFolderCreateAction`, and would create a duplicate listing within `AbstractAction._registry`. + + +from_request method +################### + +PrimAITE actions need to be have a `from_request` method, which can be passed to the `RequestManager` for processing. This allows the custom action to be actioned within the simulation environment.