From e0033de7b671eeaea3d56ab4e0b4898ad85f592c Mon Sep 17 00:00:00 2001 From: Marek Wolan Date: Fri, 12 Jan 2024 14:54:55 +0000 Subject: [PATCH 01/13] Fix folder reset --- src/primaite/simulator/file_system/folder.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/primaite/simulator/file_system/folder.py b/src/primaite/simulator/file_system/folder.py index 237a6341..027547bb 100644 --- a/src/primaite/simulator/file_system/folder.py +++ b/src/primaite/simulator/file_system/folder.py @@ -17,8 +17,6 @@ class Folder(FileSystemItemABC): files: Dict[str, File] = {} "Files stored in the folder." - _files_by_name: Dict[str, File] = {} - "Files by their name as .." deleted_files: Dict[str, File] = {} "Files that have been deleted." @@ -78,7 +76,6 @@ class Folder(FileSystemItemABC): file = self.deleted_files[uuid] self.deleted_files.pop(uuid) self.files[uuid] = file - self._files_by_name[file.name] = file # Clear any other deleted files that aren't original (have been created by agent) self.deleted_files.clear() @@ -89,7 +86,6 @@ class Folder(FileSystemItemABC): if uuid not in original_file_uuids: file = self.files[uuid] self.files.pop(uuid) - self._files_by_name.pop(file.name) # Now reset all remaining files for file in self.files.values(): @@ -219,7 +215,10 @@ class Folder(FileSystemItemABC): :return: The matching File. """ # TODO: Increment read count? - return self._files_by_name.get(file_name) + for file in self.files.values(): + if file.name == file_name: + return file + return None def get_file_by_id(self, file_uuid: str, include_deleted: Optional[bool] = False) -> File: """ @@ -250,15 +249,14 @@ class Folder(FileSystemItemABC): raise Exception(f"Invalid file: {file}") # check if file with id or name already exists in folder - if (force is not True) and file.name in self._files_by_name: + if self.get_file(file.name) is not None and not force: raise Exception(f"File with name {file.name} already exists in folder") - if (force is not True) and file.uuid in self.files: + if (file.uuid in self.files) and not force: raise Exception(f"File with uuid {file.uuid} already exists in folder") # add to list self.files[file.uuid] = file - self._files_by_name[file.name] = file self._file_request_manager.add_request(file.uuid, RequestType(func=file._request_manager)) file.folder = self @@ -275,7 +273,6 @@ class Folder(FileSystemItemABC): if self.files.get(file.uuid): self.files.pop(file.uuid) - self._files_by_name.pop(file.name) self.deleted_files[file.uuid] = file file.delete() self.sys_log.info(f"Removed file {file.name} (id: {file.uuid})") @@ -300,7 +297,6 @@ class Folder(FileSystemItemABC): self.deleted_files[file_id] = file self.files = {} - self._files_by_name = {} def restore_file(self, file_uuid: str): """ @@ -316,7 +312,6 @@ class Folder(FileSystemItemABC): file.restore() self.files[file.uuid] = file - self._files_by_name[file.name] = file if file.deleted: self.deleted_files.pop(file_uuid) From 728f80cc2162b5dc1ce7878921dd90e70ca0d740 Mon Sep 17 00:00:00 2001 From: Marek Wolan Date: Mon, 15 Jan 2024 09:48:14 +0000 Subject: [PATCH 02/13] Temporarily disable file delete action --- src/primaite/game/agent/actions.py | 10 ++++++++++ src/primaite/game/agent/rewards.py | 7 +++++++ src/primaite/simulator/file_system/folder.py | 2 +- .../system/services/database/database_service.py | 4 ++++ 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/primaite/game/agent/actions.py b/src/primaite/game/agent/actions.py index 4c47bfaa..585e2dfa 100644 --- a/src/primaite/game/agent/actions.py +++ b/src/primaite/game/agent/actions.py @@ -296,6 +296,16 @@ class NodeFileDeleteAction(NodeFileAbstractAction): 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) -> List[str]: + """Return the action formatted as a request which can be ingested by the PrimAITE simulation.""" + node_uuid = self.manager.get_node_uuid_by_idx(node_id) + folder_uuid = self.manager.get_folder_uuid_by_idx(node_idx=node_id, folder_idx=folder_id) + file_uuid = self.manager.get_file_uuid_by_idx(node_idx=node_id, folder_idx=folder_id, file_idx=file_id) + if node_uuid is None or folder_uuid is None or file_uuid is None: + return ["do_nothing"] + return ["do_nothing"] + # return ["network", "node", node_uuid, "file_system", "delete", "file", folder_uuid, file_uuid] + class NodeFileRepairAction(NodeFileAbstractAction): """Action which repairs a file.""" diff --git a/src/primaite/game/agent/rewards.py b/src/primaite/game/agent/rewards.py index 8f064be3..6cee127f 100644 --- a/src/primaite/game/agent/rewards.py +++ b/src/primaite/game/agent/rewards.py @@ -110,6 +110,13 @@ class DatabaseFileIntegrity(AbstractReward): :type state: Dict """ database_file_state = access_from_nested_dict(state, self.location_in_state) + if database_file_state is NOT_PRESENT_IN_STATE: + _LOGGER.info( + f"Could not calculate {self.__class__} reward because " + "simulation state did not contain enough information." + ) + return 0.0 + health_status = database_file_state["health_status"] if health_status == 2: return -1 diff --git a/src/primaite/simulator/file_system/folder.py b/src/primaite/simulator/file_system/folder.py index 027547bb..ab862898 100644 --- a/src/primaite/simulator/file_system/folder.py +++ b/src/primaite/simulator/file_system/folder.py @@ -276,7 +276,7 @@ class Folder(FileSystemItemABC): self.deleted_files[file.uuid] = file file.delete() self.sys_log.info(f"Removed file {file.name} (id: {file.uuid})") - self._file_request_manager.remove_request(file.uuid) + # self._file_request_manager.remove_request(file.uuid) else: _LOGGER.debug(f"File with UUID {file.uuid} was not found.") diff --git a/src/primaite/simulator/system/services/database/database_service.py b/src/primaite/simulator/system/services/database/database_service.py index 1cdd0390..6fcced25 100644 --- a/src/primaite/simulator/system/services/database/database_service.py +++ b/src/primaite/simulator/system/services/database/database_service.py @@ -84,6 +84,10 @@ class DatabaseService(Service): ftp_client_service: FTPClient = software_manager.software.get("FTPClient") # send backup copy of database file to FTP server + if not self.db_file: + self.sys_log.error("Attempted to backup database file but it doesn't exist.") + return False + response = ftp_client_service.send_file( dest_ip_address=self.backup_server_ip, src_file_name=self.db_file.name, From edc9772d0a4a452256b10b03dd839496738adda5 Mon Sep 17 00:00:00 2001 From: Marek Wolan Date: Mon, 15 Jan 2024 10:10:30 +0000 Subject: [PATCH 03/13] Fix typo in database restore --- .../simulator/system/services/database/database_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/primaite/simulator/system/services/database/database_service.py b/src/primaite/simulator/system/services/database/database_service.py index 6fcced25..14190dd2 100644 --- a/src/primaite/simulator/system/services/database/database_service.py +++ b/src/primaite/simulator/system/services/database/database_service.py @@ -125,7 +125,7 @@ class DatabaseService(Service): return False # replace db file - self.file_system.delete_file(folder_name="database", file_name="downloads.db") + self.file_system.delete_file(folder_name="database", file_name="database.db") self.file_system.copy_file(src_folder_name="downloads", src_file_name="database.db", dst_folder_name="database") if self.db_file is None: From 7d218c520115acb611f9de21a55aaea1a1964987 Mon Sep 17 00:00:00 2001 From: Marek Wolan Date: Mon, 15 Jan 2024 10:31:13 +0000 Subject: [PATCH 04/13] bump version --- src/primaite/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/primaite/VERSION b/src/primaite/VERSION index 09fb39d2..72f12ef8 100644 --- a/src/primaite/VERSION +++ b/src/primaite/VERSION @@ -1 +1 @@ -3.0.0b5 +3.0.0b6dev From 42d00e04408ed6a6857cdb67d668478eb027a733 Mon Sep 17 00:00:00 2001 From: Marek Wolan Date: Sun, 21 Jan 2024 16:33:51 +0000 Subject: [PATCH 05/13] Fix issue where file deleted flag wouldn't be reset --- .gitignore | 1 + .../simulator/file_system/file_system.py | 17 +++++++---------- .../file_system/file_system_item_abc.py | 2 +- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index 892751d9..1ce2ca9d 100644 --- a/.gitignore +++ b/.gitignore @@ -156,3 +156,4 @@ benchmark/output # src/primaite/notebooks/scratch.ipynb src/primaite/notebooks/scratch.py sandbox.py +sandbox/ diff --git a/src/primaite/simulator/file_system/file_system.py b/src/primaite/simulator/file_system/file_system.py index c2eb0d2d..149bf083 100644 --- a/src/primaite/simulator/file_system/file_system.py +++ b/src/primaite/simulator/file_system/file_system.py @@ -23,7 +23,6 @@ class FileSystem(SimComponent): "List containing all the folders in the file system." deleted_folders: Dict[str, Folder] = {} "List containing all the folders that have been deleted." - _folders_by_name: Dict[str, Folder] = {} sys_log: SysLog "Instance of SysLog used to create system logs." sim_root: Path @@ -56,7 +55,6 @@ class FileSystem(SimComponent): folder = self.deleted_folders[uuid] self.deleted_folders.pop(uuid) self.folders[uuid] = folder - self._folders_by_name[folder.name] = folder # Clear any other deleted folders that aren't original (have been created by agent) self.deleted_folders.clear() @@ -67,7 +65,6 @@ class FileSystem(SimComponent): if uuid not in original_folder_uuids: folder = self.folders[uuid] self.folders.pop(uuid) - self._folders_by_name.pop(folder.name) # Now reset all remaining folders for folder in self.folders.values(): @@ -173,7 +170,6 @@ class FileSystem(SimComponent): folder = Folder(name=folder_name, sys_log=self.sys_log) self.folders[folder.uuid] = folder - self._folders_by_name[folder.name] = folder self._folder_request_manager.add_request( name=folder.uuid, request_type=RequestType(func=folder._request_manager) ) @@ -188,14 +184,13 @@ class FileSystem(SimComponent): if folder_name == "root": self.sys_log.warning("Cannot delete the root folder.") return - folder = self._folders_by_name.get(folder_name) + folder = self.get_folder(folder_name) if folder: # set folder to deleted state folder.delete() # remove from folder list self.folders.pop(folder.uuid) - self._folders_by_name.pop(folder.name) # add to deleted list folder.remove_all_files() @@ -221,7 +216,10 @@ class FileSystem(SimComponent): :param folder_name: The folder name. :return: The matching Folder. """ - return self._folders_by_name.get(folder_name) + for folder in self.folders.values(): + if folder.name == folder_name: + return folder + return None def get_folder_by_id(self, folder_uuid: str, include_deleted: bool = False) -> Optional[Folder]: """ @@ -261,13 +259,13 @@ class FileSystem(SimComponent): """ if folder_name: # check if file with name already exists - folder = self._folders_by_name.get(folder_name) + folder = self.get_folder(folder_name) # If not then create it if not folder: folder = self.create_folder(folder_name) else: # Use root folder if folder_name not supplied - folder = self._folders_by_name["root"] + folder = self.get_folder("root") # Create the file and add it to the folder file = File( @@ -474,7 +472,6 @@ class FileSystem(SimComponent): folder.restore() self.folders[folder.uuid] = folder - self._folders_by_name[folder.name] = folder if folder.deleted: self.deleted_folders.pop(folder.uuid) 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 86cd1ee7..c3e1426b 100644 --- a/src/primaite/simulator/file_system/file_system_item_abc.py +++ b/src/primaite/simulator/file_system/file_system_item_abc.py @@ -87,7 +87,7 @@ class FileSystemItemABC(SimComponent): def set_original_state(self): """Sets the original state.""" - vals_to_keep = {"name", "health_status", "visible_health_status", "previous_hash", "revealed_to_red"} + vals_to_keep = {"name", "health_status", "visible_health_status", "previous_hash", "revealed_to_red", "deleted"} self._original_state = self.model_dump(include=vals_to_keep) def describe_state(self) -> Dict: From 8e19e05f570b57fee370190eb59384bda6adc77f Mon Sep 17 00:00:00 2001 From: Marek Wolan Date: Sun, 21 Jan 2024 17:29:19 +0000 Subject: [PATCH 06/13] Fix acl actions for blue agent. --- .../config/_package_data/example_config.yaml | 60 ++++++++++++------- src/primaite/game/agent/actions.py | 15 ++++- 2 files changed, 51 insertions(+), 24 deletions(-) diff --git a/src/primaite/config/_package_data/example_config.yaml b/src/primaite/config/_package_data/example_config.yaml index ee0eb7ff..7393f5a3 100644 --- a/src/primaite/config/_package_data/example_config.yaml +++ b/src/primaite/config/_package_data/example_config.yaml @@ -304,63 +304,63 @@ agents: action: "NODE_RESET" options: node_id: 5 - 22: + 22: # "ACL: ADDRULE - Block outgoing traffic from client 1" (not supported in Primaite) action: "NETWORK_ACL_ADDRULE" options: position: 1 permission: 2 - source_ip_id: 7 - dest_ip_id: 1 + source_ip_id: 7 # client 1 + dest_ip_id: 1 # ALL source_port_id: 1 dest_port_id: 1 protocol_id: 1 - 23: + 23: # "ACL: ADDRULE - Block outgoing traffic from client 2" (not supported in Primaite) action: "NETWORK_ACL_ADDRULE" options: - position: 1 + position: 2 permission: 2 - source_ip_id: 8 - dest_ip_id: 1 + source_ip_id: 8 # client 2 + dest_ip_id: 1 # ALL source_port_id: 1 dest_port_id: 1 protocol_id: 1 - 24: + 24: # block tcp traffic from client 1 to web app action: "NETWORK_ACL_ADDRULE" options: - position: 1 + position: 3 permission: 2 - source_ip_id: 7 - dest_ip_id: 3 + source_ip_id: 7 # client 1 + dest_ip_id: 3 # web server source_port_id: 1 dest_port_id: 1 protocol_id: 3 - 25: + 25: # block tcp traffic from client 2 to web app action: "NETWORK_ACL_ADDRULE" options: - position: 1 + position: 4 permission: 2 - source_ip_id: 8 - dest_ip_id: 3 + source_ip_id: 8 # client 2 + dest_ip_id: 3 # web server source_port_id: 1 dest_port_id: 1 protocol_id: 3 26: action: "NETWORK_ACL_ADDRULE" options: - position: 1 + position: 5 permission: 2 - source_ip_id: 7 - dest_ip_id: 4 + source_ip_id: 7 # client 1 + dest_ip_id: 4 # database source_port_id: 1 dest_port_id: 1 protocol_id: 3 27: action: "NETWORK_ACL_ADDRULE" options: - position: 1 + position: 6 permission: 2 - source_ip_id: 8 - dest_ip_id: 4 + source_ip_id: 8 # client 2 + dest_ip_id: 4 # database source_port_id: 1 dest_port_id: 1 protocol_id: 3 @@ -504,6 +504,24 @@ agents: max_services_per_node: 2 max_nics_per_node: 8 max_acl_rules: 10 + ip_address_order: + - node_ref: domain_controller + nic_num: 1 + - node_ref: web_server + nic_num: 1 + - node_ref: database_server + nic_num: 1 + - node_ref: backup_server + nic_num: 1 + - node_ref: security_suite + nic_num: 1 + - node_ref: client_1 + nic_num: 1 + - node_ref: client_2 + nic_num: 1 + - node_ref: security_suite + nic_num: 2 + reward_function: reward_components: diff --git a/src/primaite/game/agent/actions.py b/src/primaite/game/agent/actions.py index 585e2dfa..6b15c5f8 100644 --- a/src/primaite/game/agent/actions.py +++ b/src/primaite/game/agent/actions.py @@ -470,13 +470,13 @@ class NetworkACLAddRuleAction(AbstractAction): dst_ip = "ALL" return ["do_nothing"] # NOT SUPPORTED, JUST DO NOTHING IF WE COME ACROSS THIS else: - dst_ip = self.manager.get_ip_address_by_idx(dest_ip_id) + 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 == 1: dst_port = "ALL" else: - dst_port = self.manager.get_port_by_idx(dest_port_id) + dst_port = self.manager.get_port_by_idx(dest_port_id - 2) # subtract 2 to account for UNUSED=0, and ALL=1 return [ @@ -924,6 +924,15 @@ class ActionManager: :return: The constructed ActionManager. :rtype: ActionManager """ + ip_address_order = cfg["options"].pop("ip_address_order", {}) + ip_address_list = [] + for entry in ip_address_order: + node_ref = entry["node_ref"] + nic_num = entry["nic_num"] + node_obj = game.simulation.network.get_node_by_hostname(node_ref) + ip_address = node_obj.ethernet_port[nic_num].ip_address + ip_address_list.append(ip_address) + obj = cls( game=game, actions=cfg["action_list"], @@ -931,7 +940,7 @@ class ActionManager: **cfg["options"], protocols=game.options.protocols, ports=game.options.ports, - ip_address_list=None, + ip_address_list=ip_address_list or None, act_map=cfg.get("action_map"), ) From 88c1d16f1150734fa3db6c5c303ea322b25f94a6 Mon Sep 17 00:00:00 2001 From: Marek Wolan Date: Tue, 23 Jan 2024 14:34:05 +0000 Subject: [PATCH 07/13] Fix Router acl not clearing --- src/primaite/game/game.py | 30 +------ .../network/hardware/nodes/router.py | 79 ++++++++++++++++++- 2 files changed, 80 insertions(+), 29 deletions(-) diff --git a/src/primaite/game/game.py b/src/primaite/game/game.py index 08098754..159f5bbb 100644 --- a/src/primaite/game/game.py +++ b/src/primaite/game/game.py @@ -13,11 +13,9 @@ from primaite.game.agent.rewards import RewardFunction from primaite.session.io import SessionIO, SessionIOSettings from primaite.simulator.network.hardware.base import NIC, NodeOperatingState from primaite.simulator.network.hardware.nodes.computer import Computer -from primaite.simulator.network.hardware.nodes.router import ACLAction, Router +from primaite.simulator.network.hardware.nodes.router import Router from primaite.simulator.network.hardware.nodes.server import Server from primaite.simulator.network.hardware.nodes.switch import Switch -from primaite.simulator.network.transmission.network_layer import IPProtocol -from primaite.simulator.network.transmission.transport_layer import Port from primaite.simulator.sim_container import Simulation from primaite.simulator.system.applications.database_client import DatabaseClient from primaite.simulator.system.applications.web_browser import WebBrowser @@ -227,31 +225,7 @@ class PrimaiteGame: operating_state=NodeOperatingState.ON, ) elif n_type == "router": - new_node = Router( - hostname=node_cfg["hostname"], - num_ports=node_cfg.get("num_ports"), - operating_state=NodeOperatingState.ON, - ) - if "ports" in node_cfg: - for port_num, port_cfg in node_cfg["ports"].items(): - new_node.configure_port( - port=port_num, ip_address=port_cfg["ip_address"], subnet_mask=port_cfg["subnet_mask"] - ) - # new_node.enable_port(port_num) - if "acl" in node_cfg: - for r_num, r_cfg in node_cfg["acl"].items(): - # excuse the uncommon walrus operator ` := `. It's just here as a shorthand, to avoid repeating - # this: 'r_cfg.get('src_port')' - # Port/IPProtocol. TODO Refactor - new_node.acl.add_rule( - action=ACLAction[r_cfg["action"]], - src_port=None if not (p := r_cfg.get("src_port")) else Port[p], - dst_port=None if not (p := r_cfg.get("dst_port")) else Port[p], - protocol=None if not (p := r_cfg.get("protocol")) else IPProtocol[p], - src_ip_address=r_cfg.get("ip_address"), - dst_ip_address=r_cfg.get("ip_address"), - position=r_num, - ) + new_node = Router.from_config(node_cfg) else: _LOGGER.warning(f"invalid node type {n_type} in config") if "services" in node_cfg: diff --git a/src/primaite/simulator/network/hardware/nodes/router.py b/src/primaite/simulator/network/hardware/nodes/router.py index 0234934d..bb923d62 100644 --- a/src/primaite/simulator/network/hardware/nodes/router.py +++ b/src/primaite/simulator/network/hardware/nodes/router.py @@ -9,6 +9,7 @@ from prettytable import MARKDOWN, PrettyTable from primaite.simulator.core import RequestManager, RequestType, SimComponent from primaite.simulator.network.hardware.base import ARPCache, ICMP, NIC, Node +from primaite.simulator.network.hardware.node_operating_state import NodeOperatingState from primaite.simulator.network.transmission.data_link_layer import EthernetHeader, Frame from primaite.simulator.network.transmission.network_layer import ICMPPacket, ICMPType, IPPacket, IPProtocol from primaite.simulator.network.transmission.transport_layer import Port, TCPHeader @@ -89,6 +90,8 @@ class AccessControlList(SimComponent): implicit_rule: ACLRule max_acl_rules: int = 25 _acl: List[Optional[ACLRule]] = [None] * 24 + _default_config: dict[int, dict] = {} + """Config dict describing how the ACL list should look at episode start""" def __init__(self, **kwargs) -> None: if not kwargs.get("implicit_action"): @@ -110,6 +113,21 @@ class AccessControlList(SimComponent): """Reset the original state of the SimComponent.""" self.implicit_rule.reset_component_for_episode(episode) super().reset_component_for_episode(episode) + self._reset_rules_to_default() + + def _reset_rules_to_default(self) -> None: + """Clear all ACL rules and set them to the default rules config.""" + self._acl = [None] * (self.max_acl_rules - 1) + for r_num, r_cfg in self._default_config.items(): + self.add_rule( + action=ACLAction[r_cfg["action"]], + src_port=None if not (p := r_cfg.get("src_port")) else Port[p], + dst_port=None if not (p := r_cfg.get("dst_port")) else Port[p], + protocol=None if not (p := r_cfg.get("protocol")) else IPProtocol[p], + src_ip_address=r_cfg.get("ip_address"), + dst_ip_address=r_cfg.get("ip_address"), + position=r_num, + ) def _init_request_manager(self) -> RequestManager: rm = super()._init_request_manager() @@ -391,7 +409,6 @@ class RouteTable(SimComponent): sys_log: SysLog def set_original_state(self): - """Sets the original state.""" """Sets the original state.""" super().set_original_state() self._original_state["routes_orig"] = self.routes @@ -864,3 +881,63 @@ class Router(Node): ] ) print(table) + + @classmethod + def from_config(cls, cfg: dict) -> "Router": + """Create a router based on a config dict. + + Schema: + - hostname (str): unique name for this router. + - num_ports (int, optional): Number of network ports on the router. 8 by default + - ports (dict): Dict with integers from 1 - num_ports as keys. The values should be another dict specifying + ip_address and subnet_mask assigned to that ports (as strings) + - acl (dict): Dict with integers from 1 - max_acl_rules as keys. The key defines the position within the ACL + where the rule will be added (lower number is resolved first). The values should describe valid ACL + Rules as: + - action (str): either PERMIT or DENY + - src_port (str, optional): the named port such as HTTP, HTTPS, or POSTGRES_SERVER + - dst_port (str, optional): the named port such as HTTP, HTTPS, or POSTGRES_SERVER + - protocol (str, optional): the named IP protocol such as ICMP, TCP, or UDP + - src_ip_address (str, optional): IP address octet written in base 10 + - dst_ip_address (str, optional): IP address octet written in base 10 + + Example config: + ``` + { + 'hostname': 'router_1', + 'num_ports': 5, + 'ports': { + 1: { + 'ip_address' : '192.168.1.1', + 'subnet_mask' : '255.255.255.0', + } + }, + 'acl' : { + 21: {'action': 'PERMIT', 'src_port': 'HTTP', dst_port: 'HTTP'}, + 22: {'action': 'PERMIT', 'src_port': 'ARP', 'dst_port': 'ARP'}, + 23: {'action': 'PERMIT', 'protocol': 'ICMP'}, + }, + } + ``` + + :param cfg: Router config adhering to schema described in main docstring body + :type cfg: dict + :return: Configured router. + :rtype: Router + """ + new = Router( + hostname=cfg["hostname"], + num_ports=cfg.get("num_ports"), + operating_state=NodeOperatingState.ON, + ) + if "ports" in cfg: + for port_num, port_cfg in cfg["ports"].items(): + new.configure_port( + port=port_num, + ip_address=port_cfg["ip_address"], + subnet_mask=port_cfg["subnet_mask"], + ) + if "acl" in cfg: + new.acl._default_config = cfg["acl"] # save the config to allow resetting + new.acl._reset_rules_to_default() # read the config and apply rules + return new From 0a65f32adfa47cd84c640f3ae9c1b0aed0bc1b94 Mon Sep 17 00:00:00 2001 From: Marek Wolan Date: Thu, 25 Jan 2024 09:27:08 +0000 Subject: [PATCH 08/13] Fix ACL observations --- src/primaite/game/agent/observations.py | 35 +++++++++++++------ .../network/hardware/nodes/router.py | 12 +++---- 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/src/primaite/game/agent/observations.py b/src/primaite/game/agent/observations.py index cac5b91e..b7962827 100644 --- a/src/primaite/game/agent/observations.py +++ b/src/primaite/game/agent/observations.py @@ -1,5 +1,6 @@ """Manages the observation space for the agent.""" from abc import ABC, abstractmethod +from ipaddress import IPv4Address from typing import Any, Dict, List, Optional, Tuple, TYPE_CHECKING from gymnasium import spaces @@ -648,10 +649,13 @@ class AclObservation(AbstractObservation): # TODO: what if the ACL has more rules than num of max rules for obs space obs = {} - for i, rule_state in acl_state.items(): + acl_items = dict(acl_state.items()) + i = 1 # don't show rule 0 for compatibility reasons. + while i < self.num_rules + 1: + rule_state = acl_items[i] if rule_state is None: - obs[i + 1] = { - "position": i, + obs[i] = { + "position": i - 1, "permission": 0, "source_node_id": 0, "source_port": 0, @@ -660,15 +664,26 @@ class AclObservation(AbstractObservation): "protocol": 0, } else: - obs[i + 1] = { - "position": i, + src_ip = rule_state["src_ip_address"] + src_node_id = 1 if src_ip is None else self.node_to_id[IPv4Address(src_ip)] + dst_ip = rule_state["dst_ip_address"] + dst_node_ip = 1 if dst_ip is None else self.node_to_id[IPv4Address(dst_ip)] + src_port = rule_state["src_port"] + src_port_id = 1 if src_port is None else self.port_to_id[src_port] + dst_port = rule_state["dst_port"] + dst_port_id = 1 if dst_port is None else self.port_to_id[dst_port] + protocol = rule_state["protocol"] + protocol_id = 1 if protocol is None else self.protocol_to_id[protocol] + obs[i] = { + "position": i - 1, "permission": rule_state["action"], - "source_node_id": self.node_to_id[rule_state["src_ip_address"]], - "source_port": self.port_to_id[rule_state["src_port"]], - "dest_node_id": self.node_to_id[rule_state["dst_ip_address"]], - "dest_port": self.port_to_id[rule_state["dst_port"]], - "protocol": self.protocol_to_id[rule_state["protocol"]], + "source_node_id": src_node_id, + "source_port": src_port_id, + "dest_node_id": dst_node_ip, + "dest_port": dst_port_id, + "protocol": protocol_id, } + i += 1 return obs @property diff --git a/src/primaite/simulator/network/hardware/nodes/router.py b/src/primaite/simulator/network/hardware/nodes/router.py index bb923d62..0c5d0ce9 100644 --- a/src/primaite/simulator/network/hardware/nodes/router.py +++ b/src/primaite/simulator/network/hardware/nodes/router.py @@ -19,8 +19,8 @@ from primaite.simulator.system.core.sys_log import SysLog class ACLAction(Enum): """Enum for defining the ACL action types.""" - DENY = 0 PERMIT = 1 + DENY = 2 class ACLRule(SimComponent): @@ -66,11 +66,11 @@ class ACLRule(SimComponent): """ state = super().describe_state() state["action"] = self.action.value - state["protocol"] = self.protocol.value if self.protocol else None + state["protocol"] = self.protocol.name if self.protocol else None state["src_ip_address"] = str(self.src_ip_address) if self.src_ip_address else None - state["src_port"] = self.src_port.value if self.src_port else None + state["src_port"] = self.src_port.name if self.src_port else None state["dst_ip_address"] = str(self.dst_ip_address) if self.dst_ip_address else None - state["dst_port"] = self.dst_port.value if self.dst_port else None + state["dst_port"] = self.dst_port.name if self.dst_port else None return state @@ -733,8 +733,8 @@ class Router(Node): :return: A dictionary representing the current state. """ state = super().describe_state() - state["num_ports"] = (self.num_ports,) - state["acl"] = (self.acl.describe_state(),) + state["num_ports"] = self.num_ports + state["acl"] = self.acl.describe_state() return state def route_frame(self, frame: Frame, from_nic: NIC, re_attempt: bool = False) -> None: From 28acb5dcaed89364bb920591a8d1bd82f51e6da1 Mon Sep 17 00:00:00 2001 From: Marek Wolan Date: Thu, 25 Jan 2024 12:04:09 +0000 Subject: [PATCH 09/13] Populate step info in environment, and finish notebook --- .../config/_package_data/example_config.yaml | 2 +- .../example_config_2_rl_agents.yaml | 2 +- src/primaite/game/game.py | 5 +- .../notebooks/_package_data/uc2_network.png | Bin 0 -> 70887 bytes src/primaite/notebooks/uc2_demo.ipynb | 1038 +++++++++++++---- src/primaite/session/environment.py | 8 +- .../assets/configs/bad_primaite_session.yaml | 2 +- .../configs/eval_only_primaite_session.yaml | 2 +- tests/assets/configs/multi_agent_session.yaml | 2 +- .../assets/configs/test_primaite_session.yaml | 2 +- .../configs/train_only_primaite_session.yaml | 2 +- 11 files changed, 850 insertions(+), 215 deletions(-) create mode 100644 src/primaite/notebooks/_package_data/uc2_network.png diff --git a/src/primaite/config/_package_data/example_config.yaml b/src/primaite/config/_package_data/example_config.yaml index 7393f5a3..d8cd0099 100644 --- a/src/primaite/config/_package_data/example_config.yaml +++ b/src/primaite/config/_package_data/example_config.yaml @@ -31,7 +31,7 @@ game: - UDP agents: - - ref: client_1_green_user + - ref: client_2_green_user team: GREEN type: GreenWebBrowsingAgent observation_space: diff --git a/src/primaite/config/_package_data/example_config_2_rl_agents.yaml b/src/primaite/config/_package_data/example_config_2_rl_agents.yaml index c1e2ea81..6aa54487 100644 --- a/src/primaite/config/_package_data/example_config_2_rl_agents.yaml +++ b/src/primaite/config/_package_data/example_config_2_rl_agents.yaml @@ -25,7 +25,7 @@ game: - UDP agents: - - ref: client_1_green_user + - ref: client_2_green_user team: GREEN type: GreenWebBrowsingAgent observation_space: diff --git a/src/primaite/game/game.py b/src/primaite/game/game.py index 159f5bbb..146261f9 100644 --- a/src/primaite/game/game.py +++ b/src/primaite/game/game.py @@ -113,7 +113,7 @@ class PrimaiteGame: self.update_agents(sim_state) # Apply all actions to simulation as requests - self.apply_agent_actions() + agent_actions = self.apply_agent_actions() # noqa # Advance timestep self.advance_timestep() @@ -131,12 +131,15 @@ class PrimaiteGame: def apply_agent_actions(self) -> None: """Apply all actions to simulation as requests.""" + agent_actions = {} for agent in self.agents: obs = agent.observation_manager.current_observation rew = agent.reward_function.current_reward action_choice, options = agent.get_action(obs, rew) + agent_actions[agent.agent_name] = (action_choice, options) request = agent.format_request(action_choice, options) self.simulation.apply_request(request) + return agent_actions def advance_timestep(self) -> None: """Advance timestep.""" diff --git a/src/primaite/notebooks/_package_data/uc2_network.png b/src/primaite/notebooks/_package_data/uc2_network.png new file mode 100644 index 0000000000000000000000000000000000000000..20fa43c996bb7c3bce61778a7aa233ee4b8852f6 GIT binary patch literal 70887 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](_package_data/uc2_network.png)\n", + "\n", + "_(click image to enlarge)_\n", + "\n", + "The red agent deletes the contents of the database. When this happens, the web app cannot fetch data and users navigating to the website get a 404 error.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Network\n", + "\n", + "- The web server has:\n", + " - a web service that replies to user HTTP requests\n", + " - a database client that fetches data for the web service\n", + "- The database server has:\n", + " - a POSTGRES database service\n", + " - a database file which is accessed by the database service\n", + " - FTP client used for backing up the data to the backup_server\n", + "- The backup server has:\n", + " - a copy of the database file in a known good state\n", + " - FTP server that can send the backed up file back to the database server\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Green agent\n", + "\n", + "The green agent is logged onto client 2. It sometimes uses the web browser on client 2 to navigate to `http://arcd.com/users`. The web server replies with a status code 200 if the data is available on the database or 404 if not available." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Red agent\n", + "\n", + "The red agent waits a bit then sends a DELETE query to the database from client 1. If the delete is successful, the database file is flagged as compromised to signal that data is not available." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Blue agent\n", + "\n", + "The blue agent can view the entire network, but the health statuses of components are not updated until a scan is performed. The blue agent should restore the database file from backup after it was compromised. It can also prevent further attacks by blocking client 1 from reaching the database server. This can be done by removing client 1's network connection or adding ACL rules on the router to stop the packets from arriving." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Reinforcement learning details" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Scripted agents:\n", + "### Red\n", + "The red agent sits on client 1 and uses an application called DataManipulationBot whose sole purpose is to send a DELETE query to the database.\n", + "The red agent can choose one of two action each timestep:\n", + "1. do nothing\n", + "2. execute the data manipulation application\n", + "The schedule for selecting when to execute the application is controlled by three parameters:\n", + "- start time\n", + "- frequency\n", + "- variance\n", + "Attacks start at a random timestep between (start_time - variance) and (start_time + variance). After each attack, another is attempted after a random delay between (frequency - variance) and (frequency + variance) timesteps.\n", + "\n", + "The data manipulation app itself has an element of randomness because the attack has a probability of success. The default is 0.8 to succeed with the port scan step and 0.8 to succeed with the attack itself.\n", + "Upon a successful attack, the database file becomes corrupted which incurs a negative reward for the RL defender.\n", + "\n", + "The red agent does not use information about the state of the network to decide its action.\n", + "\n", + "### Green\n", + "The green agent sits on client 2 and uses the web browser application to send requests to the web server. The schedule of the green agent is currently random, meaning it will request webpage with a 50% probability, and do nothing with a 50% probability.\n", + "\n", + "When the green agent is blocked from accessing the data through the webpage, this incurs a negative reward to the RL defender." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Observation Space\n", + "\n", + "The blue agent's observation space is structured as nested dictionary with the following information:\n", + "```\n", + "\n", + "- NODES\n", + " - \n", + " - SERVICES\n", + " - \n", + " - operating_status\n", + " - health_status\n", + " - FOLDERS\n", + " - \n", + " - health_status\n", + " - FILES\n", + " - \n", + " - health_status\n", + " - NICS\n", + " - \n", + " - nic_status\n", + " - operating_status\n", + "- LINKS\n", + " - \n", + " - PROTOCOLS\n", + " - ALL\n", + " - load\n", + "- ACL\n", + " - \n", + " - position\n", + " - permission\n", + " - source_node_id\n", + " - source_port\n", + " - dest_node_id\n", + " - dest_port\n", + " - protocol\n", + "- ICS\n", + "```\n", + "\n", + "### Mappings\n", + "\n", + "The dict keys for `node_id` are in the following order:\n", + "|node_id|node name|\n", + "|--|--|\n", + "|1|domain_controller|\n", + "|2|web_server|\n", + "|3|database_server|\n", + "|4|backup_server|\n", + "|5|security_suite|\n", + "|6|client_1|\n", + "|7|client_2|\n", + "\n", + "Service 1 on node 2 (web_server) corresponds to the Web Server service. Other services are only there for padding to ensure that each node's observation space has the same shape. They are filled with zeroes.\n", + "\n", + "Folder 1 on node 3 corresponds to the database folder. File 1 in that folder corresponds to the database storage file. Other files and folders are only there for padding to ensure that each node's observation space has the same shape. They are filled with zeroes.\n", + "\n", + "The dict keys for `link_id` are in the following order:\n", + "|link_id|endpoint_a|endpoint_b|\n", + "|--|--|--|\n", + "|1|router_1|switch_1|\n", + "|1|router_1|switch_2|\n", + "|1|switch_1|domain_controller|\n", + "|1|switch_1|web_server|\n", + "|1|switch_1|database_server|\n", + "|1|switch_1|backup_server|\n", + "|1|switch_1|security_suite|\n", + "|1|switch_2|client_1|\n", + "|1|switch_2|client_2|\n", + "|1|switch_2|security_suite|\n", + "\n", + "The ACL rules in the observation space appear in the same order that they do in the actual ACL. Though, only the first 10 rules are shown, there are default rules lower down that cannot be changed by the agent. The extra rules just allow the network to function normally, by allowing pings, ARP traffic, etc.\n", + "\n", + "Most nodes have only 1 nic, so the observation for those is placed at NIC index 1 in the observation space. Only the security suite has 2 NICs, the second NIC in the observation space is the one that connects the security suite with swtich_2.\n", + "\n", + "The meaning of the services' operating_state is:\n", + "|operating_state|label|\n", + "|--|--|\n", + "|0|UNUSED|\n", + "|1|RUNNING|\n", + "|2|STOPPED|\n", + "|3|PAUSED|\n", + "|4|DISABLED|\n", + "|5|INSTALLING|\n", + "|6|RESTARTING|\n", + "\n", + "The meaning of the services' health_state is:\n", + "|health_state|label|\n", + "|--|--|\n", + "|0|UNUSED|\n", + "|1|GOOD|\n", + "|2|PATCHING|\n", + "|3|COMPROMISED|\n", + "|4|OVERWHELMED|\n", + "\n", + "The meaning of the files' and folders' health_state is:\n", + "|health_state|label|\n", + "|--|--|\n", + "|0|UNUSED|\n", + "|1|GOOD|\n", + "|2|COMPROMISED|\n", + "|3|CORRUPT|\n", + "|4|RESTORING|\n", + "|5|REPAIRING|\n", + "\n", + "The meaning of the NICs' operating_status is:\n", + "|operating_status|label|\n", + "|--|--|\n", + "|0|UNUSED|\n", + "|1|ENABLED|\n", + "|2|DISABLED|\n", + "\n", + "Link load has the following meaning:\n", + "|load|percent utilisation|\n", + "|--|--|\n", + "|0|exactly 0%|\n", + "|1|0-11%|\n", + "|2|11-22%|\n", + "|3|22-33%|\n", + "|4|33-44%|\n", + "|5|44-55%|\n", + "|6|55-66%|\n", + "|7|66-77%|\n", + "|8|77-88%|\n", + "|9|88-99%|\n", + "|10|exactly 100%|\n", + "\n", + "ACL permission has the following meaning:\n", + "|permission|label|\n", + "|--|--|\n", + "|0|UNUSED|\n", + "|1|ALLOW|\n", + "|2|DENY|\n", + "\n", + "ACL source / destination node ids actually correspond to IP addresses (since ACLs work with IP addresses)\n", + "|source / dest node id|ip_address|label|\n", + "|--|--|--|\n", + "|0| | UNUSED|\n", + "|1| |ALL addresses|\n", + "|2| 192.168.1.10 | domain_controller|\n", + "|3| 192.168.1.12 | web_server \n", + "|4| 192.168.1.14 | database_server|\n", + "|5| 192.168.1.16 | backup_server|\n", + "|6| 192.168.1.110 | security_suite (eth-1)|\n", + "|7| 192.168.10.21 | client_1|\n", + "|8| 192.168.10.22 | client_2|\n", + "|9| 192.168.10.110| security_suite (eth-2)|\n", + "\n", + "ACL source / destination port ids have the following encoding:\n", + "|port id|port number| port use |\n", + "|--|--|--|\n", + "|0||UNUSED|\n", + "|1||ALL|\n", + "|2|219|ARP|\n", + "|3|53|DNS|\n", + "|4|80|HTTP|\n", + "|5|5432|POSTGRES_SERVER|\n", + "\n", + "ACL protocol ids have the following encoding:\n", + "|protocol id|label|\n", + "|--|--|\n", + "|0|UNUSED|\n", + "|1|ALL|\n", + "|2|ICMP|\n", + "|3|TCP|\n", + "|4|UDP|\n", + "\n", + "protocol" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Action Space\n", + "\n", + "The blue agent chooses from a list of 54 pre-defined actions. The full list is defined in the `action_map` in the config. The most important ones are explained here:\n", + "\n", + "- `0`: Do nothing\n", + "- `1`: Scan the web service - this refreshes the health status in the observation space\n", + "- `9`: Scan the database file - this refreshes the health status of the database file\n", + "- `13`: Patch the database service - This triggers the database to restore data from the backup server\n", + "- `19`: Shut down client 1\n", + "- `22`: Block outgoing traffic from client 1\n", + "- `26`: Block TCP traffic from client 1 to the database node\n", + "- `28-37`: Remove ACL rules 1-10\n", + "- `42`: Disconnect client 1 from the network\n", + "\n", + "The other actions will either have no effect or will negatively impact the network, so the blue agent should avoid taking other actions, and learn about these actions." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Reward Function\n", + "\n", + "The blue agent's reward is calculated using two measures:\n", + "1. Whether the database file is in a good state (+1 for good, -1 for corrupted, 0 for any other state)\n", + "2. Whether the green agent's most recent webpage request was successful (+1 for a `200` return code, -1 for a `404` return code and 0 otherwise).\n", + "These two components are averaged to get the final reward.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Demonstration" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First, load the required modules" + ] + }, { "cell_type": "code", "execution_count": 1, "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/home/cade/repos/PrimAITE/venv/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", - " from .autonotebook import tqdm as notebook_tqdm\n", - "2023-11-26 23:25:47,985\tINFO util.py:159 -- Missing packages: ['ipywidgets']. Run `pip install -U ipywidgets`, then restart the notebook server for rich notebook output.\n", - "2023-11-26 23:25:51,213\tINFO util.py:159 -- Missing packages: ['ipywidgets']. Run `pip install -U ipywidgets`, then restart the notebook server for rich notebook output.\n", - "2023-11-26 23:25:51,491\tWARNING __init__.py:10 -- PG has/have been moved to `rllib_contrib` and will no longer be maintained by the RLlib team. You can still use it/them normally inside RLlib util Ray 2.8, but from Ray 2.9 on, all `rllib_contrib` algorithms will no longer be part of the core repo, and will therefore have to be installed separately with pinned dependencies for e.g. ray[rllib] and other packages! See https://github.com/ray-project/ray/tree/master/rllib_contrib#rllib-contrib for more information on the RLlib contrib effort.\n" - ] - } - ], + "outputs": [], "source": [ - "from primaite.session.session import PrimaiteSession\n", - "from primaite.game.game import PrimaiteGame\n", - "from primaite.config.load import example_config_path\n", - "\n", - "from primaite.simulator.system.services.database.database_service import DatabaseService\n", - "\n", - "import yaml" + "%load_ext autoreload\n", + "%autoreload 2" ] }, { @@ -36,61 +339,181 @@ "name": "stderr", "output_type": "stream", "text": [ - "2023-11-26 23:25:51,579::ERROR::primaite.simulator.network.hardware.base::175::NIC a9:92:0a:5e:1b:e4/127.0.0.1 cannot be enabled as it is not connected to a Link\n", - "2023-11-26 23:25:51,580::ERROR::primaite.simulator.network.hardware.base::175::NIC ef:03:23:af:3c:19/127.0.0.1 cannot be enabled as it is not connected to a Link\n", - "2023-11-26 23:25:51,581::ERROR::primaite.simulator.network.hardware.base::175::NIC ae:cf:83:2f:94:17/127.0.0.1 cannot be enabled as it is not connected to a Link\n", - "2023-11-26 23:25:51,582::ERROR::primaite.simulator.network.hardware.base::175::NIC 4c:b2:99:e2:4a:5d/127.0.0.1 cannot be enabled as it is not connected to a Link\n", - "2023-11-26 23:25:51,583::ERROR::primaite.simulator.network.hardware.base::175::NIC b9:eb:f9:c2:17:2f/127.0.0.1 cannot be enabled as it is not connected to a Link\n", - "2023-11-26 23:25:51,590::ERROR::primaite.simulator.network.hardware.base::175::NIC cb:df:ca:54:be:01/192.168.1.10 cannot be enabled as it is not connected to a Link\n", - "2023-11-26 23:25:51,595::ERROR::primaite.simulator.network.hardware.base::175::NIC 6e:32:12:da:4d:0d/192.168.1.12 cannot be enabled as it is not connected to a Link\n", - "2023-11-26 23:25:51,600::ERROR::primaite.simulator.network.hardware.base::175::NIC 58:6e:9b:a7:68:49/192.168.1.14 cannot be enabled as it is not connected to a Link\n", - "2023-11-26 23:25:51,604::ERROR::primaite.simulator.network.hardware.base::175::NIC 33:db:a6:40:dd:a3/192.168.1.16 cannot be enabled as it is not connected to a Link\n", - "2023-11-26 23:25:51,608::ERROR::primaite.simulator.network.hardware.base::175::NIC 72:aa:2b:c0:4c:5f/192.168.1.110 cannot be enabled as it is not connected to a Link\n", - "2023-11-26 23:25:51,610::ERROR::primaite.simulator.network.hardware.base::175::NIC 11:d7:0e:90:d9:a4/192.168.10.110 cannot be enabled as it is not connected to a Link\n", - "2023-11-26 23:25:51,614::ERROR::primaite.simulator.network.hardware.base::175::NIC 86:2b:a4:e5:4d:0f/192.168.10.21 cannot be enabled as it is not connected to a Link\n", - "2023-11-26 23:25:51,631::ERROR::primaite.simulator.network.hardware.base::175::NIC af:ad:8f:84:f1:db/192.168.10.22 cannot be enabled as it is not connected to a Link\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "installing DNSServer on node domain_controller\n", - "installing DatabaseClient on node web_server\n", - "installing WebServer on node web_server\n", - "installing DatabaseService on node database_server\n", - "installing FTPClient on node database_server\n", - "installing FTPServer on node backup_server\n", - "installing DNSClient on node client_1\n", - "installing DNSClient on node client_2\n" + "/home/cade/repos/PrimAITE/venv/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", + " from .autonotebook import tqdm as notebook_tqdm\n", + "2024-01-25 11:19:29,199\tINFO util.py:159 -- Missing packages: ['ipywidgets']. Run `pip install -U ipywidgets`, then restart the notebook server for rich notebook output.\n", + "2024-01-25 11:19:31,924\tINFO util.py:159 -- Missing packages: ['ipywidgets']. Run `pip install -U ipywidgets`, then restart the notebook server for rich notebook output.\n" ] } ], "source": [ + "# Imports\n", + "from primaite.config.load import example_config_path\n", + "from primaite.session.environment import PrimaiteGymEnv\n", + "from primaite.game.game import PrimaiteGame\n", + "import yaml\n", + "from pprint import pprint\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Instantiate the environment. We also disable the agent observation flattening.\n", "\n", - "with open(example_config_path(),'r') as cfgfile:\n", - " cfg = yaml.safe_load(cfgfile)\n", - "game = PrimaiteGame.from_config(cfg)\n", - "net = game.simulation.network\n", - "database_server = net.get_node_by_hostname('database_server')\n", - "web_server = net.get_node_by_hostname('web_server')\n", - "client_1 = net.get_node_by_hostname('client_1')\n", - "\n", - "db_service = database_server.software_manager.software[\"DatabaseService\"]\n", - "db_client = web_server.software_manager.software[\"DatabaseClient\"]\n", - "# db_client.run()\n", - "db_manipulation_bot = client_1.software_manager.software[\"DataManipulationBot\"]\n", - "db_manipulation_bot.port_scan_p_of_success=1.0\n", - "db_manipulation_bot.data_manipulation_p_of_success=1.0\n" + "This cell will print the observation when the network is healthy. You should be able to verify Node file and service statuses against the description above." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Resetting environment, episode 0, avg. reward: 0.0\n", + "env created successfully\n", + "{'ACL': {1: {'dest_node_id': 0,\n", + " 'dest_port': 0,\n", + " 'permission': 0,\n", + " 'position': 0,\n", + " 'protocol': 0,\n", + " 'source_node_id': 0,\n", + " 'source_port': 0},\n", + " 2: {'dest_node_id': 0,\n", + " 'dest_port': 0,\n", + " 'permission': 0,\n", + " 'position': 1,\n", + " 'protocol': 0,\n", + " 'source_node_id': 0,\n", + " 'source_port': 0},\n", + " 3: {'dest_node_id': 0,\n", + " 'dest_port': 0,\n", + " 'permission': 0,\n", + " 'position': 2,\n", + " 'protocol': 0,\n", + " 'source_node_id': 0,\n", + " 'source_port': 0},\n", + " 4: {'dest_node_id': 0,\n", + " 'dest_port': 0,\n", + " 'permission': 0,\n", + " 'position': 3,\n", + " 'protocol': 0,\n", + " 'source_node_id': 0,\n", + " 'source_port': 0},\n", + " 5: {'dest_node_id': 0,\n", + " 'dest_port': 0,\n", + " 'permission': 0,\n", + " 'position': 4,\n", + " 'protocol': 0,\n", + " 'source_node_id': 0,\n", + " 'source_port': 0},\n", + " 6: {'dest_node_id': 0,\n", + " 'dest_port': 0,\n", + " 'permission': 0,\n", + " 'position': 5,\n", + " 'protocol': 0,\n", + " 'source_node_id': 0,\n", + " 'source_port': 0},\n", + " 7: {'dest_node_id': 0,\n", + " 'dest_port': 0,\n", + " 'permission': 0,\n", + " 'position': 6,\n", + " 'protocol': 0,\n", + " 'source_node_id': 0,\n", + " 'source_port': 0},\n", + " 8: {'dest_node_id': 0,\n", + " 'dest_port': 0,\n", + " 'permission': 0,\n", + " 'position': 7,\n", + " 'protocol': 0,\n", + " 'source_node_id': 0,\n", + " 'source_port': 0},\n", + " 9: {'dest_node_id': 0,\n", + " 'dest_port': 0,\n", + " 'permission': 0,\n", + " 'position': 8,\n", + " 'protocol': 0,\n", + " 'source_node_id': 0,\n", + " 'source_port': 0},\n", + " 10: {'dest_node_id': 0,\n", + " 'dest_port': 0,\n", + " 'permission': 0,\n", + " 'position': 9,\n", + " 'protocol': 0,\n", + " 'source_node_id': 0,\n", + " 'source_port': 0}},\n", + " 'ICS': 0,\n", + " 'LINKS': {1: {'PROTOCOLS': {'ALL': 1}},\n", + " 2: {'PROTOCOLS': {'ALL': 1}},\n", + " 3: {'PROTOCOLS': {'ALL': 1}},\n", + " 4: {'PROTOCOLS': {'ALL': 1}},\n", + " 5: {'PROTOCOLS': {'ALL': 1}},\n", + " 6: {'PROTOCOLS': {'ALL': 1}},\n", + " 7: {'PROTOCOLS': {'ALL': 1}},\n", + " 8: {'PROTOCOLS': {'ALL': 1}},\n", + " 9: {'PROTOCOLS': {'ALL': 1}},\n", + " 10: {'PROTOCOLS': {'ALL': 1}}},\n", + " 'NODES': {1: {'FOLDERS': {1: {'FILES': {1: {'health_status': 0}},\n", + " 'health_status': 0}},\n", + " 'NICS': {1: {'nic_status': 1}, 2: {'nic_status': 0}},\n", + " 'SERVICES': {1: {'health_status': 0, 'operating_status': 1}},\n", + " 'operating_status': 1},\n", + " 2: {'FOLDERS': {1: {'FILES': {1: {'health_status': 0}},\n", + " 'health_status': 0}},\n", + " 'NICS': {1: {'nic_status': 1}, 2: {'nic_status': 0}},\n", + " 'SERVICES': {1: {'health_status': 0, 'operating_status': 1}},\n", + " 'operating_status': 1},\n", + " 3: {'FOLDERS': {1: {'FILES': {1: {'health_status': 1}},\n", + " 'health_status': 1}},\n", + " 'NICS': {1: {'nic_status': 1}, 2: {'nic_status': 0}},\n", + " 'SERVICES': {1: {'health_status': 0, 'operating_status': 0}},\n", + " 'operating_status': 1},\n", + " 4: {'FOLDERS': {1: {'FILES': {1: {'health_status': 0}},\n", + " 'health_status': 0}},\n", + " 'NICS': {1: {'nic_status': 1}, 2: {'nic_status': 0}},\n", + " 'SERVICES': {1: {'health_status': 0, 'operating_status': 0}},\n", + " 'operating_status': 1},\n", + " 5: {'FOLDERS': {1: {'FILES': {1: {'health_status': 0}},\n", + " 'health_status': 0}},\n", + " 'NICS': {1: {'nic_status': 1}, 2: {'nic_status': 0}},\n", + " 'SERVICES': {1: {'health_status': 0, 'operating_status': 0}},\n", + " 'operating_status': 1},\n", + " 6: {'FOLDERS': {1: {'FILES': {1: {'health_status': 0}},\n", + " 'health_status': 0}},\n", + " 'NICS': {1: {'nic_status': 1}, 2: {'nic_status': 0}},\n", + " 'SERVICES': {1: {'health_status': 0, 'operating_status': 0}},\n", + " 'operating_status': 1},\n", + " 7: {'FOLDERS': {1: {'FILES': {1: {'health_status': 0}},\n", + " 'health_status': 0}},\n", + " 'NICS': {1: {'nic_status': 1}, 2: {'nic_status': 0}},\n", + " 'SERVICES': {1: {'health_status': 0, 'operating_status': 0}},\n", + " 'operating_status': 1}}}\n" + ] + } + ], "source": [ - "db_client.run()" + "# create the env\n", + "with open(example_config_path(), 'r') as f:\n", + " cfg = yaml.safe_load(f)\n", + "game = PrimaiteGame.from_config(cfg)\n", + "env = PrimaiteGymEnv(game = game)\n", + "# Don't flatten obs as we are not training an agent and we wish to see the dict-formatted observations\n", + "env.agent.flatten_obs = False\n", + "obs, info = env.reset()\n", + "print('env created successfully')\n", + "pprint(obs)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The red agent will start attacking at some point between step 20 and 30.\n", + "\n", + "The red agent has a random chance of failing its attack, so you may need run the following cell multiple times until the reward goes from 1.0 to -1.0." ] }, { @@ -99,18 +522,53 @@ "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "step: 1, Red action: DONOTHING, Blue reward:1.0\n", + "step: 2, Red action: DONOTHING, Blue reward:1.0\n", + "step: 3, Red action: DONOTHING, Blue reward:1.0\n", + "step: 4, Red action: DONOTHING, Blue reward:1.0\n", + "step: 5, Red action: DONOTHING, Blue reward:1.0\n", + "step: 6, Red action: DONOTHING, Blue reward:1.0\n", + "step: 7, Red action: DONOTHING, Blue reward:1.0\n", + "step: 8, Red action: DONOTHING, Blue reward:1.0\n", + "step: 9, Red action: DONOTHING, Blue reward:1.0\n", + "step: 10, Red action: DONOTHING, Blue reward:1.0\n", + "step: 11, Red action: DONOTHING, Blue reward:1.0\n", + "step: 12, Red action: DONOTHING, Blue reward:1.0\n", + "step: 13, Red action: DONOTHING, Blue reward:1.0\n", + "step: 14, Red action: DONOTHING, Blue reward:1.0\n", + "step: 15, Red action: DONOTHING, Blue reward:1.0\n", + "step: 16, Red action: DONOTHING, Blue reward:1.0\n", + "step: 17, Red action: DONOTHING, Blue reward:1.0\n", + "step: 18, Red action: DONOTHING, Blue reward:1.0\n", + "step: 19, Red action: DONOTHING, Blue reward:1.0\n", + "step: 20, Red action: DONOTHING, Blue reward:1.0\n", + "step: 21, Red action: DONOTHING, Blue reward:1.0\n", + "step: 22, Red action: DONOTHING, Blue reward:1.0\n", + "step: 23, Red action: DONOTHING, Blue reward:1.0\n", + "step: 24, Red action: DONOTHING, Blue reward:1.0\n", + "step: 25, Red action: DONOTHING, Blue reward:1.0\n", + "step: 26, Red action: DONOTHING, Blue reward:1.0\n", + "step: 27, Red action: NODE_APPLICATION_EXECUTE, Blue reward:0.0\n", + "step: 28, Red action: DONOTHING, Blue reward:-1.0\n", + "step: 29, Red action: DONOTHING, Blue reward:-1.0\n", + "step: 30, Red action: DONOTHING, Blue reward:-1.0\n" + ] } ], "source": [ - "db_service.backup_database()" + "for step in range(30):\n", + " obs, reward, terminated, truncated, info = env.step(0)\n", + " print(f\"step: {env.game.step_counter}, Red action: {info['agent_actions']['client_1_data_manipulation_red_bot'][0]}, Blue reward:{reward}\" )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now the reward is -1, let's have a look at blue agent's observation." ] }, { @@ -119,27 +577,110 @@ "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "{1: {'FOLDERS': {1: {'FILES': {1: {'health_status': 0}}, 'health_status': 0}},\n", + " 'NICS': {1: {'nic_status': 1}, 2: {'nic_status': 0}},\n", + " 'SERVICES': {1: {'health_status': 0, 'operating_status': 1}},\n", + " 'operating_status': 1},\n", + " 2: {'FOLDERS': {1: {'FILES': {1: {'health_status': 0}}, 'health_status': 0}},\n", + " 'NICS': {1: {'nic_status': 1}, 2: {'nic_status': 0}},\n", + " 'SERVICES': {1: {'health_status': 0, 'operating_status': 1}},\n", + " 'operating_status': 1},\n", + " 3: {'FOLDERS': {1: {'FILES': {1: {'health_status': 1}}, 'health_status': 1}},\n", + " 'NICS': {1: {'nic_status': 1}, 2: {'nic_status': 0}},\n", + " 'SERVICES': {1: {'health_status': 0, 'operating_status': 0}},\n", + " 'operating_status': 1},\n", + " 4: {'FOLDERS': {1: {'FILES': {1: {'health_status': 0}}, 'health_status': 0}},\n", + " 'NICS': {1: {'nic_status': 1}, 2: {'nic_status': 0}},\n", + " 'SERVICES': {1: {'health_status': 0, 'operating_status': 0}},\n", + " 'operating_status': 1},\n", + " 5: {'FOLDERS': {1: {'FILES': {1: {'health_status': 0}}, 'health_status': 0}},\n", + " 'NICS': {1: {'nic_status': 1}, 2: {'nic_status': 0}},\n", + " 'SERVICES': {1: {'health_status': 0, 'operating_status': 0}},\n", + " 'operating_status': 1},\n", + " 6: {'FOLDERS': {1: {'FILES': {1: {'health_status': 0}}, 'health_status': 0}},\n", + " 'NICS': {1: {'nic_status': 1}, 2: {'nic_status': 0}},\n", + " 'SERVICES': {1: {'health_status': 0, 'operating_status': 0}},\n", + " 'operating_status': 1},\n", + " 7: {'FOLDERS': {1: {'FILES': {1: {'health_status': 0}}, 'health_status': 0}},\n", + " 'NICS': {1: {'nic_status': 1}, 2: {'nic_status': 0}},\n", + " 'SERVICES': {1: {'health_status': 0, 'operating_status': 0}},\n", + " 'operating_status': 1}}\n" + ] } ], "source": [ - "db_client.query(\"SELECT\")" + "pprint(obs['NODES'])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The true statuses of the database file and webapp are not updated. The blue agent needs to perform a scan to see that they have degraded." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{1: {'FOLDERS': {1: {'FILES': {1: {'health_status': 0}}, 'health_status': 0}},\n", + " 'NICS': {1: {'nic_status': 1}, 2: {'nic_status': 0}},\n", + " 'SERVICES': {1: {'health_status': 0, 'operating_status': 1}},\n", + " 'operating_status': 1},\n", + " 2: {'FOLDERS': {1: {'FILES': {1: {'health_status': 0}}, 'health_status': 0}},\n", + " 'NICS': {1: {'nic_status': 1}, 2: {'nic_status': 0}},\n", + " 'SERVICES': {1: {'health_status': 3, 'operating_status': 1}},\n", + " 'operating_status': 1},\n", + " 3: {'FOLDERS': {1: {'FILES': {1: {'health_status': 2}}, 'health_status': 1}},\n", + " 'NICS': {1: {'nic_status': 1}, 2: {'nic_status': 0}},\n", + " 'SERVICES': {1: {'health_status': 0, 'operating_status': 0}},\n", + " 'operating_status': 1},\n", + " 4: {'FOLDERS': {1: {'FILES': {1: {'health_status': 0}}, 'health_status': 0}},\n", + " 'NICS': {1: {'nic_status': 1}, 2: {'nic_status': 0}},\n", + " 'SERVICES': {1: {'health_status': 0, 'operating_status': 0}},\n", + " 'operating_status': 1},\n", + " 5: {'FOLDERS': {1: {'FILES': {1: {'health_status': 0}}, 'health_status': 0}},\n", + " 'NICS': {1: {'nic_status': 1}, 2: {'nic_status': 0}},\n", + " 'SERVICES': {1: {'health_status': 0, 'operating_status': 0}},\n", + " 'operating_status': 1},\n", + " 6: {'FOLDERS': {1: {'FILES': {1: {'health_status': 0}}, 'health_status': 0}},\n", + " 'NICS': {1: {'nic_status': 1}, 2: {'nic_status': 0}},\n", + " 'SERVICES': {1: {'health_status': 0, 'operating_status': 0}},\n", + " 'operating_status': 1},\n", + " 7: {'FOLDERS': {1: {'FILES': {1: {'health_status': 0}}, 'health_status': 0}},\n", + " 'NICS': {1: {'nic_status': 1}, 2: {'nic_status': 0}},\n", + " 'SERVICES': {1: {'health_status': 0, 'operating_status': 0}},\n", + " 'operating_status': 1}}\n" + ] + } + ], "source": [ - "db_manipulation_bot.run()" + "obs, reward, terminated, truncated, info = env.step(9) # scan database file\n", + "obs, reward, terminated, truncated, info = env.step(1) # scan webapp service\n", + "pprint(obs['NODES'])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now service 1 on node 2 has `health_status = 3`, indicating that the webapp is compromised.\n", + "File 1 in folder 1 on node 3 has `health_status = 2`, indicating that the database file is compromised." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The blue agent can now patch the database to restore the file to a good health status." ] }, { @@ -148,130 +689,221 @@ "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "False" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "db_client.query(\"SELECT\")" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "db_service.restore_backup()" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "db_client.query(\"SELECT\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "db_manipulation_bot.run()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "client_1.ping(database_server.ethernet_port[1].ip_address)" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [], - "source": [ - "from pydantic import validate_call, BaseModel" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [], - "source": [ - "class A(BaseModel):\n", - " x:int\n", - "\n", - " @validate_call\n", - " def increase_x(self, by:int) -> None:\n", - " self.x += 1" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [], - "source": [ - "my_a = A(x=3)" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "ename": "ValidationError", - "evalue": "1 validation error for increase_x\n0\n Input should be a valid integer, got a number with a fractional part [type=int_from_float, input_value=3.2, input_type=float]\n For further information visit https://errors.pydantic.dev/2.1/v/int_from_float", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mValidationError\u001b[0m Traceback (most recent call last)", - "\u001b[1;32m/home/cade/repos/PrimAITE/src/primaite/notebooks/uc2_demo.ipynb Cell 15\u001b[0m line \u001b[0;36m1\n\u001b[0;32m----> 1\u001b[0m my_a\u001b[39m.\u001b[39;49mincrease_x(\u001b[39m3.2\u001b[39;49m)\n", - "File \u001b[0;32m~/repos/PrimAITE/venv/lib/python3.10/site-packages/pydantic/_internal/_validate_call.py:91\u001b[0m, in \u001b[0;36mValidateCallWrapper.__call__\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 90\u001b[0m \u001b[39mdef\u001b[39;00m \u001b[39m__call__\u001b[39m(\u001b[39mself\u001b[39m, \u001b[39m*\u001b[39margs: Any, \u001b[39m*\u001b[39m\u001b[39m*\u001b[39mkwargs: Any) \u001b[39m-\u001b[39m\u001b[39m>\u001b[39m Any:\n\u001b[0;32m---> 91\u001b[0m res \u001b[39m=\u001b[39m \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49m__pydantic_validator__\u001b[39m.\u001b[39;49mvalidate_python(pydantic_core\u001b[39m.\u001b[39;49mArgsKwargs(args, kwargs))\n\u001b[1;32m 92\u001b[0m \u001b[39mif\u001b[39;00m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39m__return_pydantic_validator__:\n\u001b[1;32m 93\u001b[0m \u001b[39mreturn\u001b[39;00m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39m__return_pydantic_validator__\u001b[39m.\u001b[39mvalidate_python(res)\n", - "\u001b[0;31mValidationError\u001b[0m: 1 validation error for increase_x\n0\n Input should be a valid integer, got a number with a fractional part [type=int_from_float, input_value=3.2, input_type=float]\n For further information visit https://errors.pydantic.dev/2.1/v/int_from_float" + "name": "stdout", + "output_type": "stream", + "text": [ + "step: 33\n", + "Red action: DONOTHING\n", + "Green action: DONOTHING\n", + "Blue reward:-1.0\n" ] } ], "source": [ - "my_a.increase_x(3.2)" + "obs, reward, terminated, truncated, info = env.step(13) # patch the database\n", + "print(f\"step: {env.game.step_counter}\")\n", + "print(f\"Red action: {info['agent_actions']['client_1_data_manipulation_red_bot'][0]}\" )\n", + "print(f\"Green action: {info['agent_actions']['client_2_green_user'][0]}\" )\n", + "print(f\"Blue reward:{reward}\" )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The patching takes two steps, so the reward hasn't changed yet. Let's do nothing for another timestep, the reward should improve.\n", + "\n", + "The reward will be 0 as soon as the file finishes restoring. Then, the reward will increase to 1 when the green agent makes a request. (Because the webapp access part of the reward does not update until a successful request is made.)\n", + "\n", + "Run the following cell until the green action is `NODE_APPLICATION_EXECUTE`, then the reward should become 1. If you run it enough times, another red attack will happen and the reward will drop again." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "step: 44\n", + "Red action: DONOTHING\n", + "Green action: NODE_APPLICATION_EXECUTE\n", + "Blue reward:-1.0\n" + ] + } + ], + "source": [ + "obs, reward, terminated, truncated, info = env.step(0) # patch the database\n", + "print(f\"step: {env.game.step_counter}\")\n", + "print(f\"Red action: {info['agent_actions']['client_1_data_manipulation_red_bot'][0]}\" )\n", + "print(f\"Green action: {info['agent_actions']['client_2_green_user'][0]}\" )\n", + "print(f\"Blue reward:{reward}\" )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The blue agent can prevent attacks by implementing an ACL rule to stop client_1 from sending POSTGRES traffic to the database. (Let's also patch the database file to get the reward back up.)" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "step: 107, Red action: DONOTHING, Blue reward:1.0\n", + "step: 108, Red action: DONOTHING, Blue reward:1.0\n", + "step: 109, Red action: DONOTHING, Blue reward:1.0\n", + "step: 110, Red action: DONOTHING, Blue reward:1.0\n", + "step: 111, Red action: DONOTHING, Blue reward:1.0\n", + "step: 112, Red action: DONOTHING, Blue reward:1.0\n", + "step: 113, Red action: DONOTHING, Blue reward:1.0\n", + "step: 114, Red action: NODE_APPLICATION_EXECUTE, Blue reward:1.0\n", + "step: 115, Red action: DONOTHING, Blue reward:1.0\n", + "step: 116, Red action: DONOTHING, Blue reward:1.0\n", + "step: 117, Red action: DONOTHING, Blue reward:1.0\n", + "step: 118, Red action: DONOTHING, Blue reward:1.0\n", + "step: 119, Red action: DONOTHING, Blue reward:1.0\n", + "step: 120, Red action: DONOTHING, Blue reward:1.0\n", + "step: 121, Red action: DONOTHING, Blue reward:1.0\n", + "step: 122, Red action: DONOTHING, Blue reward:1.0\n", + "step: 123, Red action: DONOTHING, Blue reward:1.0\n", + "step: 124, Red action: DONOTHING, Blue reward:1.0\n", + "step: 125, Red action: DONOTHING, Blue reward:1.0\n", + "step: 126, Red action: DONOTHING, Blue reward:1.0\n", + "step: 127, Red action: DONOTHING, Blue reward:1.0\n", + "step: 128, Red action: DONOTHING, Blue reward:1.0\n", + "step: 129, Red action: DONOTHING, Blue reward:1.0\n", + "step: 130, Red action: DONOTHING, Blue reward:1.0\n", + "step: 131, Red action: DONOTHING, Blue reward:1.0\n", + "step: 132, Red action: DONOTHING, Blue reward:1.0\n", + "step: 133, Red action: DONOTHING, Blue reward:1.0\n", + "step: 134, Red action: NODE_APPLICATION_EXECUTE, Blue reward:1.0\n", + "step: 135, Red action: DONOTHING, Blue reward:1.0\n", + "step: 136, Red action: DONOTHING, Blue reward:1.0\n" + ] + } + ], + "source": [ + "env.step(13) # Patch the database\n", + "print(f\"step: {env.game.step_counter}, Red action: {info['agent_actions']['client_1_data_manipulation_red_bot'][0]}, Blue reward:{reward}\" )\n", + "\n", + "env.step(26) # Block client 1\n", + "print(f\"step: {env.game.step_counter}, Red action: {info['agent_actions']['client_1_data_manipulation_red_bot'][0]}, Blue reward:{reward}\" )\n", + "\n", + "for step in range(30):\n", + " obs, reward, terminated, truncated, info = env.step(0) # do nothing\n", + " print(f\"step: {env.game.step_counter}, Red action: {info['agent_actions']['client_1_data_manipulation_red_bot'][0]}, Blue reward:{reward}\" )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, even though the red agent executes an attack, the reward stays at 1.0" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's also have a look at the ACL observation to verify our new ACL rule at position 5." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{1: {'position': 0,\n", + " 'permission': 0,\n", + " 'source_node_id': 0,\n", + " 'source_port': 0,\n", + " 'dest_node_id': 0,\n", + " 'dest_port': 0,\n", + " 'protocol': 0},\n", + " 2: {'position': 1,\n", + " 'permission': 0,\n", + " 'source_node_id': 0,\n", + " 'source_port': 0,\n", + " 'dest_node_id': 0,\n", + " 'dest_port': 0,\n", + " 'protocol': 0},\n", + " 3: {'position': 2,\n", + " 'permission': 0,\n", + " 'source_node_id': 0,\n", + " 'source_port': 0,\n", + " 'dest_node_id': 0,\n", + " 'dest_port': 0,\n", + " 'protocol': 0},\n", + " 4: {'position': 3,\n", + " 'permission': 0,\n", + " 'source_node_id': 0,\n", + " 'source_port': 0,\n", + " 'dest_node_id': 0,\n", + " 'dest_port': 0,\n", + " 'protocol': 0},\n", + " 5: {'position': 4,\n", + " 'permission': 2,\n", + " 'source_node_id': 7,\n", + " 'source_port': 1,\n", + " 'dest_node_id': 4,\n", + " 'dest_port': 1,\n", + " 'protocol': 3},\n", + " 6: {'position': 5,\n", + " 'permission': 0,\n", + " 'source_node_id': 0,\n", + " 'source_port': 0,\n", + " 'dest_node_id': 0,\n", + " 'dest_port': 0,\n", + " 'protocol': 0},\n", + " 7: {'position': 6,\n", + " 'permission': 0,\n", + " 'source_node_id': 0,\n", + " 'source_port': 0,\n", + " 'dest_node_id': 0,\n", + " 'dest_port': 0,\n", + " 'protocol': 0},\n", + " 8: {'position': 7,\n", + " 'permission': 0,\n", + " 'source_node_id': 0,\n", + " 'source_port': 0,\n", + " 'dest_node_id': 0,\n", + " 'dest_port': 0,\n", + " 'protocol': 0},\n", + " 9: {'position': 8,\n", + " 'permission': 0,\n", + " 'source_node_id': 0,\n", + " 'source_port': 0,\n", + " 'dest_node_id': 0,\n", + " 'dest_port': 0,\n", + " 'protocol': 0},\n", + " 10: {'position': 9,\n", + " 'permission': 0,\n", + " 'source_node_id': 0,\n", + " 'source_port': 0,\n", + " 'dest_node_id': 0,\n", + " 'dest_port': 0,\n", + " 'protocol': 0}}" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "obs['ACL']" ] }, { diff --git a/src/primaite/session/environment.py b/src/primaite/session/environment.py index 6701f183..a3831bc1 100644 --- a/src/primaite/session/environment.py +++ b/src/primaite/session/environment.py @@ -29,7 +29,7 @@ class PrimaiteGymEnv(gymnasium.Env): # make ProxyAgent store the action chosen my the RL policy self.agent.store_action(action) # apply_agent_actions accesses the action we just stored - self.game.apply_agent_actions() + agent_actions = self.game.apply_agent_actions() self.game.advance_timestep() state = self.game.get_sim_state() @@ -39,7 +39,7 @@ class PrimaiteGymEnv(gymnasium.Env): reward = self.agent.reward_function.current_reward terminated = False truncated = self.game.calculate_truncated() - info = {} + info = {"agent_actions": agent_actions} # tell us what all the agents did for convenience. if self.game.save_step_metadata: self._write_step_metadata_json(action, state, reward) return next_obs, reward, terminated, truncated, info @@ -172,7 +172,7 @@ class PrimaiteRayMARLEnv(MultiAgentEnv): # 1. Perform actions for agent_name, action in actions.items(): self.agents[agent_name].store_action(action) - self.game.apply_agent_actions() + agent_actions = self.game.apply_agent_actions() # 2. Advance timestep self.game.advance_timestep() @@ -186,7 +186,7 @@ class PrimaiteRayMARLEnv(MultiAgentEnv): rewards = {name: agent.reward_function.current_reward for name, agent in self.agents.items()} terminateds = {name: False for name, _ in self.agents.items()} truncateds = {name: self.game.calculate_truncated() for name, _ in self.agents.items()} - infos = {} + infos = {"agent_actions": agent_actions} terminateds["__all__"] = len(self.terminateds) == len(self.agents) truncateds["__all__"] = self.game.calculate_truncated() if self.game.save_step_metadata: diff --git a/tests/assets/configs/bad_primaite_session.yaml b/tests/assets/configs/bad_primaite_session.yaml index 9070f246..e5458670 100644 --- a/tests/assets/configs/bad_primaite_session.yaml +++ b/tests/assets/configs/bad_primaite_session.yaml @@ -19,7 +19,7 @@ game: - UDP agents: - - ref: client_1_green_user + - ref: client_2_green_user team: GREEN type: GreenWebBrowsingAgent observation_space: diff --git a/tests/assets/configs/eval_only_primaite_session.yaml b/tests/assets/configs/eval_only_primaite_session.yaml index e67f6606..767279ce 100644 --- a/tests/assets/configs/eval_only_primaite_session.yaml +++ b/tests/assets/configs/eval_only_primaite_session.yaml @@ -23,7 +23,7 @@ game: - UDP agents: - - ref: client_1_green_user + - ref: client_2_green_user team: GREEN type: GreenWebBrowsingAgent observation_space: diff --git a/tests/assets/configs/multi_agent_session.yaml b/tests/assets/configs/multi_agent_session.yaml index 220ca21e..6290fa53 100644 --- a/tests/assets/configs/multi_agent_session.yaml +++ b/tests/assets/configs/multi_agent_session.yaml @@ -29,7 +29,7 @@ game: - UDP agents: - - ref: client_1_green_user + - ref: client_2_green_user team: GREEN type: GreenWebBrowsingAgent observation_space: diff --git a/tests/assets/configs/test_primaite_session.yaml b/tests/assets/configs/test_primaite_session.yaml index d7e94cb6..89b88475 100644 --- a/tests/assets/configs/test_primaite_session.yaml +++ b/tests/assets/configs/test_primaite_session.yaml @@ -27,7 +27,7 @@ game: - UDP agents: - - ref: client_1_green_user + - ref: client_2_green_user team: GREEN type: GreenWebBrowsingAgent observation_space: diff --git a/tests/assets/configs/train_only_primaite_session.yaml b/tests/assets/configs/train_only_primaite_session.yaml index b89349c0..b9fa1216 100644 --- a/tests/assets/configs/train_only_primaite_session.yaml +++ b/tests/assets/configs/train_only_primaite_session.yaml @@ -23,7 +23,7 @@ game: - UDP agents: - - ref: client_1_green_user + - ref: client_2_green_user team: GREEN type: GreenWebBrowsingAgent observation_space: From 99723b6578d332d49eb739bb51bda1c0d1a5ef99 Mon Sep 17 00:00:00 2001 From: Marek Wolan Date: Thu, 25 Jan 2024 12:33:18 +0000 Subject: [PATCH 10/13] Update notebook with more images. --- .../notebooks/_package_data/uc2_attack.png | Bin 0 -> 112286 bytes src/primaite/notebooks/uc2_demo.ipynb | 6 +++++- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 src/primaite/notebooks/_package_data/uc2_attack.png diff --git a/src/primaite/notebooks/_package_data/uc2_attack.png b/src/primaite/notebooks/_package_data/uc2_attack.png new file mode 100644 index 0000000000000000000000000000000000000000..8b8df5ce8ddf74ad717c734cd52945164580f0f7 GIT binary patch literal 112286 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

q7w?w|>VNDk-YR_&_h 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 literal 0 HcmV?d00001 diff --git a/src/primaite/notebooks/uc2_demo.ipynb b/src/primaite/notebooks/uc2_demo.ipynb index 7bcfdd29..6a20e3e8 100644 --- a/src/primaite/notebooks/uc2_demo.ipynb +++ b/src/primaite/notebooks/uc2_demo.ipynb @@ -55,7 +55,11 @@ "source": [ "## Red agent\n", "\n", - "The red agent waits a bit then sends a DELETE query to the database from client 1. If the delete is successful, the database file is flagged as compromised to signal that data is not available." + "The red agent waits a bit then sends a DELETE query to the database from client 1. If the delete is successful, the database file is flagged as compromised to signal that data is not available.\n", + "\n", + "[](_package_data/uc2_attack.png)\n", + "\n", + "_(click image to enlarge)_" ] }, { From e7aac754a08772f771c4dc895376eb89c453afe1 Mon Sep 17 00:00:00 2001 From: Marek Wolan Date: Thu, 25 Jan 2024 12:38:28 +0000 Subject: [PATCH 11/13] Update the changelog --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 227cec69..cb2f418b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +- Fixed a bug where ACL rules were not resetting on episode reset. +- Fixed a bug where blue agent's ACL actions were being applied against the wrong IP addresses +- Fixed a bug where deleted files and folders did not reset correctly on episode reset. +- Fixed a bug where service health status was using the actual health state instead of the visible health state +- Fixed a bug where the database file health status was using the incorrect value for negative rewards +- Fixed a bug preventing file actions from reaching their intended file +- Made database patch correctly take 2 timesteps instead of being immediate +- Made database patch only possible when the software is compromised or good, it's no longer possible when the software is OFF or RESETTING +- Temporarily disable the blue agent file delete action due to crashes. This issue is resolved in another branch that will be merged into dev soon. +- Fix a bug where ACLs were not showing up correctly in the observation space. +- Added a notebook which explains Data manipulation scenario, demonstrates the attack, and shows off blue agent's action space, observation space, and reward function. - Made packet capture and system logging optional (off by default). To turn on, change the io_settings.save_pcap_logs and io_settings.save_sys_logs settings in the config. - Made observation space flattening optional (on by default). To turn off for an agent, change the agent_settings.flatten_obs setting in the config. - Fixed an issue where the data manipulation attack was triggered at episode start. From 73a75c497b612bbacb352373070c7391205d4c73 Mon Sep 17 00:00:00 2001 From: Marek Wolan Date: Thu, 25 Jan 2024 13:13:50 +0000 Subject: [PATCH 12/13] Fix test --- .../network/hardware/nodes/router.py | 21 ++++++++++-- .../assets/configs/bad_primaite_session.yaml | 17 ++++++++++ .../configs/eval_only_primaite_session.yaml | 17 ++++++++++ tests/assets/configs/multi_agent_session.yaml | 34 +++++++++++++++++++ .../assets/configs/test_primaite_session.yaml | 17 ++++++++++ .../configs/train_only_primaite_session.yaml | 17 ++++++++++ .../_simulator/_network/test_container.py | 24 ++++++++++--- 7 files changed, 140 insertions(+), 7 deletions(-) diff --git a/src/primaite/simulator/network/hardware/nodes/router.py b/src/primaite/simulator/network/hardware/nodes/router.py index 0c5d0ce9..41c14967 100644 --- a/src/primaite/simulator/network/hardware/nodes/router.py +++ b/src/primaite/simulator/network/hardware/nodes/router.py @@ -90,7 +90,7 @@ class AccessControlList(SimComponent): implicit_rule: ACLRule max_acl_rules: int = 25 _acl: List[Optional[ACLRule]] = [None] * 24 - _default_config: dict[int, dict] = {} + _default_config: Dict[int, dict] = {} """Config dict describing how the ACL list should look at episode start""" def __init__(self, **kwargs) -> None: @@ -109,6 +109,21 @@ class AccessControlList(SimComponent): vals_to_keep = {"implicit_action", "max_acl_rules", "acl"} self._original_state = self.model_dump(include=vals_to_keep, exclude_none=True) + for i, rule in enumerate(self._acl): + if not rule: + continue + self._default_config[i] = {"action": rule.action.name} + if rule.src_ip_address: + self._default_config[i]["src_ip"] = str(rule.src_ip_address) + if rule.dst_ip_address: + self._default_config[i]["dst_ip"] = str(rule.dst_ip_address) + if rule.src_port: + self._default_config[i]["src_port"] = rule.src_port.name + if rule.dst_port: + self._default_config[i]["dst_port"] = rule.dst_port.name + if rule.protocol: + self._default_config[i]["protocol"] = rule.protocol.name + def reset_component_for_episode(self, episode: int): """Reset the original state of the SimComponent.""" self.implicit_rule.reset_component_for_episode(episode) @@ -124,8 +139,8 @@ class AccessControlList(SimComponent): src_port=None if not (p := r_cfg.get("src_port")) else Port[p], dst_port=None if not (p := r_cfg.get("dst_port")) else Port[p], protocol=None if not (p := r_cfg.get("protocol")) else IPProtocol[p], - src_ip_address=r_cfg.get("ip_address"), - dst_ip_address=r_cfg.get("ip_address"), + src_ip_address=r_cfg.get("src_ip"), + dst_ip_address=r_cfg.get("dst_ip"), position=r_num, ) diff --git a/tests/assets/configs/bad_primaite_session.yaml b/tests/assets/configs/bad_primaite_session.yaml index e5458670..4a1fc275 100644 --- a/tests/assets/configs/bad_primaite_session.yaml +++ b/tests/assets/configs/bad_primaite_session.yaml @@ -491,6 +491,23 @@ agents: max_services_per_node: 2 max_nics_per_node: 8 max_acl_rules: 10 + ip_address_order: + - node_ref: domain_controller + nic_num: 1 + - node_ref: web_server + nic_num: 1 + - node_ref: database_server + nic_num: 1 + - node_ref: backup_server + nic_num: 1 + - node_ref: security_suite + nic_num: 1 + - node_ref: client_1 + nic_num: 1 + - node_ref: client_2 + nic_num: 1 + - node_ref: security_suite + nic_num: 2 reward_function: reward_components: diff --git a/tests/assets/configs/eval_only_primaite_session.yaml b/tests/assets/configs/eval_only_primaite_session.yaml index 767279ce..c8ffa23f 100644 --- a/tests/assets/configs/eval_only_primaite_session.yaml +++ b/tests/assets/configs/eval_only_primaite_session.yaml @@ -502,6 +502,23 @@ agents: max_services_per_node: 2 max_nics_per_node: 8 max_acl_rules: 10 + ip_address_order: + - node_ref: domain_controller + nic_num: 1 + - node_ref: web_server + nic_num: 1 + - node_ref: database_server + nic_num: 1 + - node_ref: backup_server + nic_num: 1 + - node_ref: security_suite + nic_num: 1 + - node_ref: client_1 + nic_num: 1 + - node_ref: client_2 + nic_num: 1 + - node_ref: security_suite + nic_num: 2 reward_function: reward_components: diff --git a/tests/assets/configs/multi_agent_session.yaml b/tests/assets/configs/multi_agent_session.yaml index 6290fa53..6cd22694 100644 --- a/tests/assets/configs/multi_agent_session.yaml +++ b/tests/assets/configs/multi_agent_session.yaml @@ -509,6 +509,23 @@ agents: max_services_per_node: 2 max_nics_per_node: 8 max_acl_rules: 10 + ip_address_order: + - node_ref: domain_controller + nic_num: 1 + - node_ref: web_server + nic_num: 1 + - node_ref: database_server + nic_num: 1 + - node_ref: backup_server + nic_num: 1 + - node_ref: security_suite + nic_num: 1 + - node_ref: client_1 + nic_num: 1 + - node_ref: client_2 + nic_num: 1 + - node_ref: security_suite + nic_num: 2 reward_function: reward_components: @@ -940,6 +957,23 @@ agents: max_services_per_node: 2 max_nics_per_node: 8 max_acl_rules: 10 + ip_address_order: + - node_ref: domain_controller + nic_num: 1 + - node_ref: web_server + nic_num: 1 + - node_ref: database_server + nic_num: 1 + - node_ref: backup_server + nic_num: 1 + - node_ref: security_suite + nic_num: 1 + - node_ref: client_1 + nic_num: 1 + - node_ref: client_2 + nic_num: 1 + - node_ref: security_suite + nic_num: 2 reward_function: reward_components: diff --git a/tests/assets/configs/test_primaite_session.yaml b/tests/assets/configs/test_primaite_session.yaml index 89b88475..99087798 100644 --- a/tests/assets/configs/test_primaite_session.yaml +++ b/tests/assets/configs/test_primaite_session.yaml @@ -507,6 +507,23 @@ agents: max_services_per_node: 2 max_nics_per_node: 8 max_acl_rules: 10 + ip_address_order: + - node_ref: domain_controller + nic_num: 1 + - node_ref: web_server + nic_num: 1 + - node_ref: database_server + nic_num: 1 + - node_ref: backup_server + nic_num: 1 + - node_ref: security_suite + nic_num: 1 + - node_ref: client_1 + nic_num: 1 + - node_ref: client_2 + nic_num: 1 + - node_ref: security_suite + nic_num: 2 reward_function: reward_components: diff --git a/tests/assets/configs/train_only_primaite_session.yaml b/tests/assets/configs/train_only_primaite_session.yaml index b9fa1216..c2842a06 100644 --- a/tests/assets/configs/train_only_primaite_session.yaml +++ b/tests/assets/configs/train_only_primaite_session.yaml @@ -503,6 +503,23 @@ agents: max_services_per_node: 2 max_nics_per_node: 8 max_acl_rules: 10 + ip_address_order: + - node_ref: domain_controller + nic_num: 1 + - node_ref: web_server + nic_num: 1 + - node_ref: database_server + nic_num: 1 + - node_ref: backup_server + nic_num: 1 + - node_ref: security_suite + nic_num: 1 + - node_ref: client_1 + nic_num: 1 + - node_ref: client_2 + nic_num: 1 + - node_ref: security_suite + nic_num: 2 reward_function: reward_components: diff --git a/tests/unit_tests/_primaite/_simulator/_network/test_container.py b/tests/unit_tests/_primaite/_simulator/_network/test_container.py index e348838e..7667a59f 100644 --- a/tests/unit_tests/_primaite/_simulator/_network/test_container.py +++ b/tests/unit_tests/_primaite/_simulator/_network/test_container.py @@ -10,6 +10,22 @@ from primaite.simulator.system.applications.database_client import DatabaseClien from primaite.simulator.system.services.database.database_service import DatabaseService +def filter_keys_nested_item(data, keys): + stack = [(data, {})] + while stack: + current, filtered = stack.pop() + if isinstance(current, dict): + for k, v in current.items(): + if k in keys: + filtered[k] = filter_keys_nested_item(v, keys) + elif isinstance(v, (dict, list)): + stack.append((v, {})) + elif isinstance(current, list): + for item in current: + stack.append((item, {})) + return filtered + + @pytest.fixture(scope="function") def network(example_network) -> Network: assert len(example_network.routers) is 1 @@ -59,10 +75,10 @@ def test_reset_network(network): assert client_1.operating_state is NodeOperatingState.ON assert server_1.operating_state is NodeOperatingState.ON - - assert json.dumps(network.describe_state(), sort_keys=True, indent=2) == json.dumps( - state_before, sort_keys=True, indent=2 - ) + # don't worry if UUIDs change + a = filter_keys_nested_item(json.dumps(network.describe_state(), sort_keys=True, indent=2), ["uuid"]) + b = filter_keys_nested_item(json.dumps(state_before, sort_keys=True, indent=2), ["uuid"]) + assert a == b def test_creating_container(): From 4b98c1f630feccfde1eb12f5b3cebffef8170345 Mon Sep 17 00:00:00 2001 From: Marek Wolan Date: Thu, 25 Jan 2024 14:43:49 +0000 Subject: [PATCH 13/13] Update uc2 notebook --- src/primaite/notebooks/uc2_demo.ipynb | 115 ++++++++++++++------------ 1 file changed, 60 insertions(+), 55 deletions(-) diff --git a/src/primaite/notebooks/uc2_demo.ipynb b/src/primaite/notebooks/uc2_demo.ipynb index 6a20e3e8..679e8226 100644 --- a/src/primaite/notebooks/uc2_demo.ipynb +++ b/src/primaite/notebooks/uc2_demo.ipynb @@ -345,8 +345,8 @@ "text": [ "/home/cade/repos/PrimAITE/venv/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", " from .autonotebook import tqdm as notebook_tqdm\n", - "2024-01-25 11:19:29,199\tINFO util.py:159 -- Missing packages: ['ipywidgets']. Run `pip install -U ipywidgets`, then restart the notebook server for rich notebook output.\n", - "2024-01-25 11:19:31,924\tINFO util.py:159 -- Missing packages: ['ipywidgets']. Run `pip install -U ipywidgets`, then restart the notebook server for rich notebook output.\n" + "2024-01-25 14:43:32,056\tINFO util.py:159 -- Missing packages: ['ipywidgets']. Run `pip install -U ipywidgets`, then restart the notebook server for rich notebook output.\n", + "2024-01-25 14:43:35,213\tINFO util.py:159 -- Missing packages: ['ipywidgets']. Run `pip install -U ipywidgets`, then restart the notebook server for rich notebook output.\n" ] } ], @@ -502,6 +502,9 @@ "# create the env\n", "with open(example_config_path(), 'r') as f:\n", " cfg = yaml.safe_load(f)\n", + " # set success probability to 1.0 to avoid rerunning cells.\n", + " cfg['simulation']['network']['nodes'][8]['applications'][0]['options']['data_manipulation_p_of_success'] = 1.0\n", + " cfg['simulation']['network']['nodes'][8]['applications'][0]['options']['port_scan_p_of_success'] = 1.0\n", "game = PrimaiteGame.from_config(cfg)\n", "env = PrimaiteGymEnv(game = game)\n", "# Don't flatten obs as we are not training an agent and we wish to see the dict-formatted observations\n", @@ -515,9 +518,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The red agent will start attacking at some point between step 20 and 30.\n", - "\n", - "The red agent has a random chance of failing its attack, so you may need run the following cell multiple times until the reward goes from 1.0 to -1.0." + "The red agent will start attacking at some point between step 20 and 30. When this happens, the reward will go from 1.0 to 0.0, and to -1.0 when the green agent tries to access the webpage." ] }, { @@ -529,10 +530,10 @@ "name": "stdout", "output_type": "stream", "text": [ - "step: 1, Red action: DONOTHING, Blue reward:1.0\n", - "step: 2, Red action: DONOTHING, Blue reward:1.0\n", - "step: 3, Red action: DONOTHING, Blue reward:1.0\n", - "step: 4, Red action: DONOTHING, Blue reward:1.0\n", + "step: 1, Red action: DONOTHING, Blue reward:0.5\n", + "step: 2, Red action: DONOTHING, Blue reward:0.5\n", + "step: 3, Red action: DONOTHING, Blue reward:0.5\n", + "step: 4, Red action: DONOTHING, Blue reward:0.5\n", "step: 5, Red action: DONOTHING, Blue reward:1.0\n", "step: 6, Red action: DONOTHING, Blue reward:1.0\n", "step: 7, Red action: DONOTHING, Blue reward:1.0\n", @@ -550,20 +551,22 @@ "step: 19, Red action: DONOTHING, Blue reward:1.0\n", "step: 20, Red action: DONOTHING, Blue reward:1.0\n", "step: 21, Red action: DONOTHING, Blue reward:1.0\n", - "step: 22, Red action: DONOTHING, Blue reward:1.0\n", - "step: 23, Red action: DONOTHING, Blue reward:1.0\n", - "step: 24, Red action: DONOTHING, Blue reward:1.0\n", - "step: 25, Red action: DONOTHING, Blue reward:1.0\n", - "step: 26, Red action: DONOTHING, Blue reward:1.0\n", - "step: 27, Red action: NODE_APPLICATION_EXECUTE, Blue reward:0.0\n", + "step: 22, Red action: NODE_APPLICATION_EXECUTE, Blue reward:0.0\n", + "step: 23, Red action: DONOTHING, Blue reward:0.0\n", + "step: 24, Red action: DONOTHING, Blue reward:0.0\n", + "step: 25, Red action: DONOTHING, Blue reward:0.0\n", + "step: 26, Red action: DONOTHING, Blue reward:-1.0\n", + "step: 27, Red action: DONOTHING, Blue reward:-1.0\n", "step: 28, Red action: DONOTHING, Blue reward:-1.0\n", "step: 29, Red action: DONOTHING, Blue reward:-1.0\n", - "step: 30, Red action: DONOTHING, Blue reward:-1.0\n" + "step: 30, Red action: DONOTHING, Blue reward:-1.0\n", + "step: 31, Red action: DONOTHING, Blue reward:-1.0\n", + "step: 32, Red action: DONOTHING, Blue reward:-1.0\n" ] } ], "source": [ - "for step in range(30):\n", + "for step in range(32):\n", " obs, reward, terminated, truncated, info = env.step(0)\n", " print(f\"step: {env.game.step_counter}, Red action: {info['agent_actions']['client_1_data_manipulation_red_bot'][0]}, Blue reward:{reward}\" )" ] @@ -696,9 +699,9 @@ "name": "stdout", "output_type": "stream", "text": [ - "step: 33\n", + "step: 35\n", "Red action: DONOTHING\n", - "Green action: DONOTHING\n", + "Green action: NODE_APPLICATION_EXECUTE\n", "Blue reward:-1.0\n" ] } @@ -724,17 +727,17 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "step: 44\n", + "step: 36\n", "Red action: DONOTHING\n", "Green action: NODE_APPLICATION_EXECUTE\n", - "Blue reward:-1.0\n" + "Blue reward:0.0\n" ] } ], @@ -755,43 +758,45 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "step: 107, Red action: DONOTHING, Blue reward:1.0\n", - "step: 108, Red action: DONOTHING, Blue reward:1.0\n", - "step: 109, Red action: DONOTHING, Blue reward:1.0\n", - "step: 110, Red action: DONOTHING, Blue reward:1.0\n", - "step: 111, Red action: DONOTHING, Blue reward:1.0\n", - "step: 112, Red action: DONOTHING, Blue reward:1.0\n", - "step: 113, Red action: DONOTHING, Blue reward:1.0\n", - "step: 114, Red action: NODE_APPLICATION_EXECUTE, Blue reward:1.0\n", - "step: 115, Red action: DONOTHING, Blue reward:1.0\n", - "step: 116, Red action: DONOTHING, Blue reward:1.0\n", - "step: 117, Red action: DONOTHING, Blue reward:1.0\n", - "step: 118, Red action: DONOTHING, Blue reward:1.0\n", - "step: 119, Red action: DONOTHING, Blue reward:1.0\n", - "step: 120, Red action: DONOTHING, Blue reward:1.0\n", - "step: 121, Red action: DONOTHING, Blue reward:1.0\n", - "step: 122, Red action: DONOTHING, Blue reward:1.0\n", - "step: 123, Red action: DONOTHING, Blue reward:1.0\n", - "step: 124, Red action: DONOTHING, Blue reward:1.0\n", - "step: 125, Red action: DONOTHING, Blue reward:1.0\n", - "step: 126, Red action: DONOTHING, Blue reward:1.0\n", - "step: 127, Red action: DONOTHING, Blue reward:1.0\n", - "step: 128, Red action: DONOTHING, Blue reward:1.0\n", - "step: 129, Red action: DONOTHING, Blue reward:1.0\n", - "step: 130, Red action: DONOTHING, Blue reward:1.0\n", - "step: 131, Red action: DONOTHING, Blue reward:1.0\n", - "step: 132, Red action: DONOTHING, Blue reward:1.0\n", - "step: 133, Red action: DONOTHING, Blue reward:1.0\n", - "step: 134, Red action: NODE_APPLICATION_EXECUTE, Blue reward:1.0\n", - "step: 135, Red action: DONOTHING, Blue reward:1.0\n", - "step: 136, Red action: DONOTHING, Blue reward:1.0\n" + "step: 37, Red action: DONOTHING, Blue reward:0.0\n", + "step: 38, Red action: DONOTHING, Blue reward:0.0\n", + "step: 39, Red action: DONOTHING, Blue reward:1.0\n", + "step: 40, Red action: DONOTHING, Blue reward:1.0\n", + "step: 41, Red action: DONOTHING, Blue reward:1.0\n", + "step: 42, Red action: DONOTHING, Blue reward:1.0\n", + "step: 43, Red action: DONOTHING, Blue reward:1.0\n", + "step: 44, Red action: DONOTHING, Blue reward:1.0\n", + "step: 45, Red action: DONOTHING, Blue reward:1.0\n", + "step: 46, Red action: NODE_APPLICATION_EXECUTE, Blue reward:1.0\n", + "step: 47, Red action: DONOTHING, Blue reward:1.0\n", + "step: 48, Red action: DONOTHING, Blue reward:1.0\n", + "step: 49, Red action: DONOTHING, Blue reward:1.0\n", + "step: 50, Red action: DONOTHING, Blue reward:1.0\n", + "step: 51, Red action: DONOTHING, Blue reward:1.0\n", + "step: 52, Red action: DONOTHING, Blue reward:1.0\n", + "step: 53, Red action: DONOTHING, Blue reward:1.0\n", + "step: 54, Red action: DONOTHING, Blue reward:1.0\n", + "step: 55, Red action: DONOTHING, Blue reward:1.0\n", + "step: 56, Red action: DONOTHING, Blue reward:1.0\n", + "step: 57, Red action: DONOTHING, Blue reward:1.0\n", + "step: 58, Red action: DONOTHING, Blue reward:1.0\n", + "step: 59, Red action: DONOTHING, Blue reward:1.0\n", + "step: 60, Red action: DONOTHING, Blue reward:1.0\n", + "step: 61, Red action: DONOTHING, Blue reward:1.0\n", + "step: 62, Red action: DONOTHING, Blue reward:1.0\n", + "step: 63, Red action: DONOTHING, Blue reward:1.0\n", + "step: 64, Red action: DONOTHING, Blue reward:1.0\n", + "step: 65, Red action: DONOTHING, Blue reward:1.0\n", + "step: 66, Red action: DONOTHING, Blue reward:1.0\n", + "step: 67, Red action: DONOTHING, Blue reward:1.0\n", + "step: 68, Red action: DONOTHING, Blue reward:1.0\n" ] } ], @@ -823,7 +828,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 10, "metadata": {}, "outputs": [ { @@ -901,7 +906,7 @@ " 'protocol': 0}}" ] }, - "execution_count": 24, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" }