diff --git a/CHANGELOG.md b/CHANGELOG.md index c712ef66..7a0ef4c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - 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. ### Added - Network Hardware - Added base hardware module with NIC, SwitchPort, Node, and Link. Nodes have diff --git a/src/primaite/game/agent/data_manipulation_bot.py b/src/primaite/game/agent/data_manipulation_bot.py index 8237ce06..7ad45518 100644 --- a/src/primaite/game/agent/data_manipulation_bot.py +++ b/src/primaite/game/agent/data_manipulation_bot.py @@ -15,7 +15,6 @@ class DataManipulationAgent(AbstractScriptedAgent): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self._set_next_execution_timestep(self.agent_settings.start_settings.start_step) def _set_next_execution_timestep(self, timestep: int) -> None: @@ -46,3 +45,8 @@ class DataManipulationAgent(AbstractScriptedAgent): self._set_next_execution_timestep(current_timestep + self.agent_settings.start_settings.frequency) return "NODE_APPLICATION_EXECUTE", {"node_id": 0, "application_id": 0} + + def reset_agent_for_episode(self) -> None: + """Set the next execution timestep when the episode resets.""" + super().reset_agent_for_episode() + self._set_next_execution_timestep(self.agent_settings.start_settings.start_step) diff --git a/src/primaite/game/agent/interface.py b/src/primaite/game/agent/interface.py index 8657fc45..8b6dd6d4 100644 --- a/src/primaite/game/agent/interface.py +++ b/src/primaite/game/agent/interface.py @@ -135,6 +135,10 @@ class AbstractAgent(ABC): request = self.action_manager.form_request(action_identifier=action, action_options=options) return request + def reset_agent_for_episode(self) -> None: + """Agent reset logic should go here.""" + pass + class AbstractScriptedAgent(AbstractAgent): """Base class for actors which generate their own behaviour.""" diff --git a/src/primaite/game/agent/observations.py b/src/primaite/game/agent/observations.py index 767514b4..eecf4163 100644 --- a/src/primaite/game/agent/observations.py +++ b/src/primaite/game/agent/observations.py @@ -140,7 +140,10 @@ class ServiceObservation(AbstractObservation): service_state = access_from_nested_dict(state, self.where) if service_state is NOT_PRESENT_IN_STATE: return self.default_observation - return {"operating_status": service_state["operating_state"], "health_status": service_state["health_state"]} + return { + "operating_status": service_state["operating_state"], + "health_status": service_state["health_state_visible"], + } @property def space(self) -> spaces.Space: diff --git a/src/primaite/game/agent/rewards.py b/src/primaite/game/agent/rewards.py index 9b3dfb80..cb8f8cb1 100644 --- a/src/primaite/game/agent/rewards.py +++ b/src/primaite/game/agent/rewards.py @@ -181,7 +181,6 @@ class WebServer404Penalty(AbstractReward): """ web_service_state = access_from_nested_dict(state, self.location_in_state) if web_service_state is NOT_PRESENT_IN_STATE: - print("error getting web service state") return 0.0 most_recent_return_code = web_service_state["last_response_status_code"] # TODO: reward needs to use the current web state. Observation should return web state at the time of last scan. diff --git a/src/primaite/game/game.py b/src/primaite/game/game.py index 586bca79..08098754 100644 --- a/src/primaite/game/game.py +++ b/src/primaite/game/game.py @@ -162,6 +162,7 @@ class PrimaiteGame: self.simulation.reset_component_for_episode(episode=self.episode_counter) for agent in self.agents: agent.reward_function.total_reward = 0.0 + agent.reset_agent_for_episode() def close(self) -> None: """Close the game, this will close the simulation.""" diff --git a/src/primaite/simulator/system/services/red_services/data_manipulation_bot.py b/src/primaite/simulator/system/services/red_services/data_manipulation_bot.py index 44a56cf1..fcd9a3cc 100644 --- a/src/primaite/simulator/system/services/red_services/data_manipulation_bot.py +++ b/src/primaite/simulator/system/services/red_services/data_manipulation_bot.py @@ -73,7 +73,7 @@ class DataManipulationBot(DatabaseClient): def _init_request_manager(self) -> RequestManager: rm = super()._init_request_manager() - rm.add_request(name="execute", request_type=RequestType(func=lambda request, context: self.run())) + rm.add_request(name="execute", request_type=RequestType(func=lambda request, context: self.attack())) return rm @@ -169,6 +169,12 @@ class DataManipulationBot(DatabaseClient): Calls the parent classes execute method before starting the application loop. """ super().run() + + def attack(self): + """Perform the attack steps after opening the application.""" + if not self._can_perform_action(): + _LOGGER.debug("Data manipulation application attempted to execute but it cannot perform actions right now.") + self.run() self._application_loop() def _application_loop(self): @@ -199,4 +205,4 @@ class DataManipulationBot(DatabaseClient): :param timestep: The timestep value to update the bot's state. """ - self._application_loop() + pass diff --git a/src/primaite/simulator/system/software.py b/src/primaite/simulator/system/software.py index 048e6fec..7be270c0 100644 --- a/src/primaite/simulator/system/software.py +++ b/src/primaite/simulator/system/software.py @@ -36,12 +36,12 @@ class SoftwareHealthState(Enum): "Unused state." GOOD = 1 "The software is in a good and healthy condition." - COMPROMISED = 2 - "The software's security has been compromised." - OVERWHELMED = 3 - "he software is overwhelmed and not functioning properly." - PATCHING = 4 + PATCHING = 2 "The software is undergoing patching or updates." + COMPROMISED = 3 + "The software's security has been compromised." + OVERWHELMED = 4 + "he software is overwhelmed and not functioning properly." class SoftwareCriticality(Enum): @@ -145,8 +145,8 @@ class Software(SimComponent): state = super().describe_state() state.update( { - "health_state": self.health_state_actual.value, - "health_state_red_view": self.health_state_visible.value, + "health_state_actual": self.health_state_actual.value, + "health_state_visible": self.health_state_visible.value, "criticality": self.criticality.value, "patching_count": self.patching_count, "scanning_count": self.scanning_count, @@ -278,7 +278,7 @@ class IOSoftware(Software): Returns true if the software can perform actions. """ - if self.software_manager and self.software_manager.node.operating_state is NodeOperatingState.OFF: + if self.software_manager and self.software_manager.node.operating_state is not NodeOperatingState.ON: _LOGGER.debug(f"{self.name} Error: {self.software_manager.node.hostname} is not online.") return False return True