From 98ca33e9949e5d47fc3f9564a10ea7dd47a7cac0 Mon Sep 17 00:00:00 2001 From: "Czar.Echavez" Date: Mon, 30 Oct 2023 15:34:13 +0000 Subject: [PATCH] #1961: scanning no longer happens every timestep - the scan is all done in one timestep after the required timestep countdown is complete --- .../simulator/file_system/file_system.py | 87 +++++++++++++------ .../simulator/network/hardware/base.py | 80 ++++++++++------- .../_file_system/test_file_system.py | 35 ++++++-- .../_file_system/test_file_system_actions.py | 11 +-- .../_network/_hardware/test_node_actions.py | 10 ++- 5 files changed, 150 insertions(+), 73 deletions(-) diff --git a/src/primaite/simulator/file_system/file_system.py b/src/primaite/simulator/file_system/file_system.py index af76254d..4ffe6a6d 100644 --- a/src/primaite/simulator/file_system/file_system.py +++ b/src/primaite/simulator/file_system/file_system.py @@ -98,6 +98,7 @@ class FileSystemItemABC(SimComponent): state["status"] = self.health_status.name state["visible_status"] = self.visible_health_status.name state["previous_hash"] = self.previous_hash + state["revealed_to_red"] = self.revealed_to_red return state def _init_request_manager(self) -> RequestManager: @@ -124,10 +125,6 @@ class FileSystemItemABC(SimComponent): """ return convert_size(self.size) - def reveal_to_red(self): - """Reveals the folder/file to the red agent.""" - self.revealed_to_red = True - @abstractmethod def check_hash(self) -> bool: """ @@ -246,15 +243,23 @@ class FileSystem(SimComponent): for folder_id in self.folders: self.folders[folder_id].apply_timestep(timestep=timestep) - def scan(self): - """Scan all the folders (and child files) in the file system.""" - for folder_id in self.folders: - self.folders[folder_id].scan() + def scan(self, instant_scan: bool = False): + """ + Scan all the folders (and child files) in the file system. - def reveal_to_red(self): - """Reveals all the folders (and child files) in the file system to the red agent.""" + :param: instant_scan: If True, the scan is completed instantly and ignores scan duration. Default False. + """ for folder_id in self.folders: - self.folders[folder_id].reveal_to_red() + self.folders[folder_id].scan(instant_scan=instant_scan) + + def reveal_to_red(self, instant_scan: bool = False): + """ + Reveals all the folders (and child files) in the file system to the red agent. + + :param: instant_scan: If True, the scan is completed instantly and ignores scan duration. Default False. + """ + for folder_id in self.folders: + self.folders[folder_id].reveal_to_red(instant_scan=instant_scan) def create_folder(self, folder_name: str) -> Folder: """ @@ -529,20 +534,25 @@ class Folder(FileSystemItemABC): # scan files each timestep if self.scan_duration >= 0: - # scan one file per timestep - file = self.get_file_by_id(file_uuid=list(self.files)[self.scan_duration]) - file.scan() - if file.visible_health_status == FileSystemItemHealthStatus.CORRUPT: - self.visible_health_status = FileSystemItemHealthStatus.CORRUPT self.scan_duration -= 1 + if self.scan_duration == 0: + for file_id in self.files: + file = self.get_file_by_id(file_uuid=file_id) + file.scan() + if file.visible_health_status == FileSystemItemHealthStatus.CORRUPT: + self.visible_health_status = FileSystemItemHealthStatus.CORRUPT + # red scan file at each step if self.red_scan_duration >= 0: - # scan one file per timestep - file = self.get_file_by_id(file_uuid=list(self.files)[self.red_scan_duration]) - file.reveal_to_red() self.red_scan_duration -= 1 + if self.red_scan_duration == 0: + self.revealed_to_red = True + for file_id in self.files: + file = self.get_file_by_id(file_uuid=file_id) + file.reveal_to_red() + # apply timestep to files in folder for file_id in self.files: self.files[file_id].apply_timestep(timestep=timestep) @@ -641,23 +651,44 @@ class Folder(FileSystemItemABC): """Returns true if the folder is being quarantined.""" pass - def scan(self) -> None: - """Update Folder visible status.""" + def scan(self, instant_scan: bool = False) -> None: + """ + Update Folder visible status. + + :param: instant_scan: If True, the scan is completed instantly and ignores scan duration. Default False. + """ + if instant_scan: + for file_id in self.files: + file = self.get_file_by_id(file_uuid=file_id) + file.scan() + if file.visible_health_status == FileSystemItemHealthStatus.CORRUPT: + self.visible_health_status = FileSystemItemHealthStatus.CORRUPT + return + if self.scan_duration <= 0: # scan one file per timestep - self.scan_duration = len(self.files) - 1 + self.scan_duration = len(self.files) self.fs.sys_log.info(f"Scanning folder {self.name} (id: {self.uuid})") else: # scan already in progress self.fs.sys_log.info(f"Scan is already in progress {self.name} (id: {self.uuid})") - def reveal_to_red(self): - """Reveals the folders and files to the red agent.""" - super().reveal_to_red() + def reveal_to_red(self, instant_scan: bool = False): + """ + Reveals the folders and files to the red agent. + + :param: instant_scan: If True, the scan is completed instantly and ignores scan duration. Default False. + """ + if instant_scan: + self.revealed_to_red = True + for file_id in self.files: + file = self.get_file_by_id(file_uuid=file_id) + file.reveal_to_red() + return if self.red_scan_duration <= 0: # scan one file per timestep - self.red_scan_duration = len(self.files) - 1 + self.red_scan_duration = len(self.files) self.fs.sys_log.info(f"Folder revealed to red agent: {self.name} (id: {self.uuid})") else: # scan already in progress @@ -830,6 +861,10 @@ class File(FileSystemItemABC): self.folder.fs.sys_log.info(f"Scanning file {self.sim_path if self.sim_path else path}") self.visible_health_status = self.health_status + def reveal_to_red(self): + """Reveals the folder/file to the red agent.""" + self.revealed_to_red = True + def check_hash(self) -> bool: """ Check if the file has been changed. diff --git a/src/primaite/simulator/network/hardware/base.py b/src/primaite/simulator/network/hardware/base.py index 945eb345..9dc1a2ff 100644 --- a/src/primaite/simulator/network/hardware/base.py +++ b/src/primaite/simulator/network/hardware/base.py @@ -929,6 +929,15 @@ class Node(SimComponent): is_resetting: bool = False "If true, the node will try turning itself off then back on again." + node_scan_duration: int = 10 + "How many timesteps until the whole node is scanned. Default 10 time steps." + + node_scan_countdown: int = 0 + "Time steps until scan is complete" + + red_scan_countdown: int = 0 + "Time steps until reveal to red scan is complete." + def __init__(self, **kwargs): """ Initialize the Node with various components and managers. @@ -1098,8 +1107,47 @@ class Node(SimComponent): self.is_resetting = False self.power_on() - # apply time step to node components + # time steps which require the node to be on if self.operating_state == NodeOperatingState.ON: + # node scanning + if self.node_scan_countdown > 0: + self.node_scan_countdown -= 1 + + if self.node_scan_countdown == 0: + # scan everything! + for process_id in self.processes: + self.processes[process_id].scan() + + # scan services + for service_id in self.services: + self.services[service_id].scan() + + # scan applications + for application_id in self.applications: + self.applications[application_id].scan() + + # scan file system + self.file_system.scan(instant_scan=True) + + if self.red_scan_countdown > 0: + self.red_scan_countdown -= 1 + + if self.red_scan_countdown == 0: + # scan processes + for process_id in self.processes: + self.processes[process_id].reveal_to_red() + + # scan services + for service_id in self.services: + self.services[service_id].reveal_to_red() + + # scan applications + for application_id in self.applications: + self.applications[application_id].reveal_to_red() + + # scan file system + self.file_system.reveal_to_red(instant_scan=True) + for process_id in self.processes: self.processes[process_id].apply_timestep(timestep=timestep) @@ -1124,20 +1172,7 @@ class Node(SimComponent): to the red agent. """ - # scan processes - for process_id in self.processes: - self.processes[process_id].scan() - - # scan services - for service_id in self.services: - self.services[service_id].scan() - - # scan applications - for application_id in self.applications: - self.applications[application_id].scan() - - # scan file system - self.file_system.scan() + self.node_scan_countdown = self.node_scan_duration def reveal_to_red(self) -> None: """ @@ -1152,20 +1187,7 @@ class Node(SimComponent): `revealed_to_red` to `True`. """ - # scan processes - for process_id in self.processes: - self.processes[process_id].reveal_to_red() - - # scan services - for service_id in self.services: - self.services[service_id].reveal_to_red() - - # scan applications - for application_id in self.applications: - self.applications[application_id].reveal_to_red() - - # scan file system - self.file_system.reveal_to_red() + self.red_scan_countdown = self.node_scan_duration def power_on(self): """Power on the Node, enabling its NICs if it is in the OFF state.""" diff --git a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system.py b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system.py index 2404f30d..12d9b94c 100644 --- a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system.py +++ b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system.py @@ -215,8 +215,8 @@ def test_folder_scan(file_system): folder.apply_timestep(timestep=0) assert folder.health_status == FileSystemItemHealthStatus.CORRUPT - assert folder.visible_health_status == FileSystemItemHealthStatus.CORRUPT - assert file1.visible_health_status == FileSystemItemHealthStatus.CORRUPT + assert folder.visible_health_status == FileSystemItemHealthStatus.GOOD + assert file1.visible_health_status == FileSystemItemHealthStatus.GOOD assert file2.visible_health_status == FileSystemItemHealthStatus.GOOD folder.apply_timestep(timestep=1) @@ -226,12 +226,33 @@ def test_folder_scan(file_system): assert file1.visible_health_status == FileSystemItemHealthStatus.CORRUPT assert file2.visible_health_status == FileSystemItemHealthStatus.CORRUPT - folder.apply_timestep(timestep=2) - assert folder.health_status == FileSystemItemHealthStatus.CORRUPT - assert folder.visible_health_status == FileSystemItemHealthStatus.CORRUPT - assert file1.visible_health_status == FileSystemItemHealthStatus.CORRUPT - assert file2.visible_health_status == FileSystemItemHealthStatus.CORRUPT +def test_folder_reveal_to_red_scan(file_system): + """Test the ability to reveal files to red.""" + folder: Folder = file_system.create_folder(folder_name="test_folder") + file_system.create_file(file_name="test_file.txt", folder_name="test_folder") + file_system.create_file(file_name="test_file2.txt", folder_name="test_folder") + + file1: File = folder.get_file_by_id(file_uuid=list(folder.files)[1]) + file2: File = folder.get_file_by_id(file_uuid=list(folder.files)[0]) + + assert folder.revealed_to_red is False + assert file1.revealed_to_red is False + assert file2.revealed_to_red is False + + folder.reveal_to_red() + + folder.apply_timestep(timestep=0) + + assert folder.revealed_to_red is False + assert file1.revealed_to_red is False + assert file2.revealed_to_red is False + + folder.apply_timestep(timestep=1) + + assert folder.revealed_to_red is True + assert file1.revealed_to_red is True + assert file2.revealed_to_red is True def test_simulated_file_check_hash(file_system): diff --git a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_actions.py b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_actions.py index 23115fd7..abfb244a 100644 --- a/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_actions.py +++ b/tests/unit_tests/_primaite/_simulator/_file_system/test_file_system_actions.py @@ -47,8 +47,8 @@ def test_folder_scan_request(populated_file_system): folder.apply_timestep(timestep=0) assert folder.health_status == FileSystemItemHealthStatus.CORRUPT - assert folder.visible_health_status == FileSystemItemHealthStatus.CORRUPT - assert file1.visible_health_status == FileSystemItemHealthStatus.CORRUPT + assert folder.visible_health_status == FileSystemItemHealthStatus.GOOD + assert file1.visible_health_status == FileSystemItemHealthStatus.GOOD assert file2.visible_health_status == FileSystemItemHealthStatus.GOOD folder.apply_timestep(timestep=1) @@ -58,13 +58,6 @@ def test_folder_scan_request(populated_file_system): assert file1.visible_health_status == FileSystemItemHealthStatus.CORRUPT assert file2.visible_health_status == FileSystemItemHealthStatus.CORRUPT - folder.apply_timestep(timestep=2) - - assert folder.health_status == FileSystemItemHealthStatus.CORRUPT - assert folder.visible_health_status == FileSystemItemHealthStatus.CORRUPT - assert file1.visible_health_status == FileSystemItemHealthStatus.CORRUPT - assert file2.visible_health_status == FileSystemItemHealthStatus.CORRUPT - def test_file_checkhash_request(populated_file_system): """Test that an agent can request a file hash check.""" diff --git a/tests/unit_tests/_primaite/_simulator/_network/_hardware/test_node_actions.py b/tests/unit_tests/_primaite/_simulator/_network/_hardware/test_node_actions.py index 6161bbf6..3d6eea3b 100644 --- a/tests/unit_tests/_primaite/_simulator/_network/_hardware/test_node_actions.py +++ b/tests/unit_tests/_primaite/_simulator/_network/_hardware/test_node_actions.py @@ -69,14 +69,16 @@ def test_node_os_scan(node, service, application): assert folder.visible_health_status == FileSystemItemHealthStatus.GOOD file: File = node.file_system.create_file(folder_name="test_folder", file_name="file.txt") + file2: File = node.file_system.create_file(folder_name="test_folder", file_name="file2.txt") file.corrupt() + file2.corrupt() assert file.visible_health_status == FileSystemItemHealthStatus.GOOD # run os scan node.apply_request(["os", "scan"]) # apply time steps - for i in range(20): + for i in range(10): node.apply_timestep(timestep=i) # should update the state of all items @@ -85,6 +87,7 @@ def test_node_os_scan(node, service, application): assert application.health_state_visible == SoftwareHealthState.COMPROMISED assert folder.visible_health_status == FileSystemItemHealthStatus.CORRUPT assert file.visible_health_status == FileSystemItemHealthStatus.CORRUPT + assert file2.visible_health_status == FileSystemItemHealthStatus.CORRUPT def test_node_red_scan(node, service, application): @@ -108,13 +111,15 @@ def test_node_red_scan(node, service, application): assert folder.revealed_to_red is False file: File = node.file_system.create_file(folder_name="test_folder", file_name="file.txt") + file2: File = node.file_system.create_file(folder_name="test_folder", file_name="file2.txt") assert file.revealed_to_red is False + assert file2.revealed_to_red is False # run os scan node.apply_request(["scan"]) # apply time steps - for i in range(20): + for i in range(10): node.apply_timestep(timestep=i) # should update the state of all items @@ -123,6 +128,7 @@ def test_node_red_scan(node, service, application): assert application.revealed_to_red is True assert folder.revealed_to_red is True assert file.revealed_to_red is True + assert file2.revealed_to_red is True def test_reset_node(node):