Add service patch and fix other bugs

This commit is contained in:
Marek Wolan
2023-12-21 15:07:41 +00:00
parent e33f74e3f2
commit 96f8435c5e
6 changed files with 135 additions and 60 deletions

View File

@@ -169,6 +169,7 @@ agents:
- type: NODE_SERVICE_RESTART - type: NODE_SERVICE_RESTART
- type: NODE_SERVICE_DISABLE - type: NODE_SERVICE_DISABLE
- type: NODE_SERVICE_ENABLE - type: NODE_SERVICE_ENABLE
- type: NODE_SERVICE_PATCH
- type: NODE_FILE_SCAN - type: NODE_FILE_SCAN
- type: NODE_FILE_CHECKHASH - type: NODE_FILE_CHECKHASH
- type: NODE_FILE_DELETE - type: NODE_FILE_DELETE
@@ -199,111 +200,110 @@ agents:
1: 1:
action: NODE_SERVICE_SCAN action: NODE_SERVICE_SCAN
options: options:
node_id: 2 node_id: 1
service_id: 1 service_id: 0
# stop webapp service # stop webapp service
2: 2:
action: NODE_SERVICE_STOP action: NODE_SERVICE_STOP
options: options:
node_id: 2 node_id: 1
service_id: 1 service_id: 0
# start webapp service # start webapp service
3: 3:
action: "NODE_SERVICE_START" action: "NODE_SERVICE_START"
options: options:
node_id: 2 node_id: 1
service_id: 1 service_id: 0
4: 4:
action: "NODE_SERVICE_PAUSE" action: "NODE_SERVICE_PAUSE"
options: options:
node_id: 2 node_id: 1
service_id: 1 service_id: 0
5: 5:
action: "NODE_SERVICE_RESUME" action: "NODE_SERVICE_RESUME"
options: options:
node_id: 2 node_id: 1
service_id: 1 service_id: 0
6: 6:
action: "NODE_SERVICE_RESTART" action: "NODE_SERVICE_RESTART"
options: options:
node_id: 2 node_id: 1
service_id: 1 service_id: 0
7: 7:
action: "NODE_SERVICE_DISABLE" action: "NODE_SERVICE_DISABLE"
options: options:
node_id: 2 node_id: 1
service_id: 1 service_id: 0
8: 8:
action: "NODE_SERVICE_ENABLE" action: "NODE_SERVICE_ENABLE"
options: options:
node_id: 2 node_id: 1
service_id: 1 service_id: 0
9: 9: # check database.db file
action: "NODE_FILE_SCAN" action: "NODE_FILE_SCAN"
options: options:
node_id: 3 node_id: 2
folder_id: 1 folder_id: 1
file_id: 1 file_id: 0
10: 10:
action: "NODE_FILE_CHECKHASH" action: "NODE_FILE_CHECKHASH"
options: options:
node_id: 3 node_id: 2
folder_id: 1 folder_id: 1
file_id: 1 file_id: 0
11: 11:
action: "NODE_FILE_DELETE" action: "NODE_FILE_DELETE"
options: options:
node_id: 3 node_id: 2
folder_id: 1 folder_id: 1
file_id: 1 file_id: 0
12: 12:
action: "NODE_FILE_REPAIR" action: "NODE_FILE_REPAIR"
options: options:
node_id: 3 node_id: 2
folder_id: 1 folder_id: 1
file_id: 1 file_id: 0
13: 13:
action: "NODE_FILE_RESTORE" action: "NODE_SERVICE_PATCH"
options: options:
node_id: 3 node_id: 2
folder_id: 1 service_id: 0
file_id: 1
14: 14:
action: "NODE_FOLDER_SCAN" action: "NODE_FOLDER_SCAN"
options: options:
node_id: 3 node_id: 2
folder_id: 1 folder_id: 1
15: 15:
action: "NODE_FOLDER_CHECKHASH" action: "NODE_FOLDER_CHECKHASH"
options: options:
node_id: 3 node_id: 2
folder_id: 1 folder_id: 1
16: 16:
action: "NODE_FOLDER_REPAIR" action: "NODE_FOLDER_REPAIR"
options: options:
node_id: 3 node_id: 2
folder_id: 1 folder_id: 1
17: 17:
action: "NODE_FOLDER_RESTORE" action: "NODE_FOLDER_RESTORE"
options: options:
node_id: 3 node_id: 2
folder_id: 1 folder_id: 1
18: 18:
action: "NODE_OS_SCAN" action: "NODE_OS_SCAN"
options: options:
node_id: 3 node_id: 2
19: 19: # shutdown client 1
action: "NODE_SHUTDOWN" action: "NODE_SHUTDOWN"
options: options:
node_id: 6 node_id: 5
20: 20:
action: "NODE_STARTUP" action: "NODE_STARTUP"
options: options:
node_id: 6 node_id: 5
21: 21:
action: "NODE_RESET" action: "NODE_RESET"
options: options:
node_id: 6 node_id: 5
22: 22:
action: "NETWORK_ACL_ADDRULE" action: "NETWORK_ACL_ADDRULE"
options: options:
@@ -407,93 +407,94 @@ agents:
38: 38:
action: "NETWORK_NIC_DISABLE" action: "NETWORK_NIC_DISABLE"
options: options:
node_id: 1 node_id: 0
nic_id: 1 nic_id: 1
39: 39:
action: "NETWORK_NIC_ENABLE" action: "NETWORK_NIC_ENABLE"
options: options:
node_id: 1 node_id: 0
nic_id: 1 nic_id: 1
40: 40:
action: "NETWORK_NIC_DISABLE" action: "NETWORK_NIC_DISABLE"
options: options:
node_id: 2 node_id: 1
nic_id: 1 nic_id: 1
41: 41:
action: "NETWORK_NIC_ENABLE" action: "NETWORK_NIC_ENABLE"
options: options:
node_id: 2 node_id: 1
nic_id: 1 nic_id: 1
42: 42:
action: "NETWORK_NIC_DISABLE" action: "NETWORK_NIC_DISABLE"
options: options:
node_id: 3 node_id: 2
nic_id: 1 nic_id: 1
43: 43:
action: "NETWORK_NIC_ENABLE" action: "NETWORK_NIC_ENABLE"
options: options:
node_id: 3 node_id: 2
nic_id: 1 nic_id: 1
44: 44:
action: "NETWORK_NIC_DISABLE" action: "NETWORK_NIC_DISABLE"
options: options:
node_id: 4 node_id: 3
nic_id: 1 nic_id: 1
45: 45:
action: "NETWORK_NIC_ENABLE" action: "NETWORK_NIC_ENABLE"
options: options:
node_id: 4 node_id: 3
nic_id: 1 nic_id: 1
46: 46:
action: "NETWORK_NIC_DISABLE" action: "NETWORK_NIC_DISABLE"
options: options:
node_id: 5 node_id: 4
nic_id: 1 nic_id: 1
47: 47:
action: "NETWORK_NIC_ENABLE" action: "NETWORK_NIC_ENABLE"
options: options:
node_id: 5 node_id: 4
nic_id: 1 nic_id: 1
48: 48:
action: "NETWORK_NIC_DISABLE" action: "NETWORK_NIC_DISABLE"
options: options:
node_id: 5 node_id: 4
nic_id: 2 nic_id: 2
49: 49:
action: "NETWORK_NIC_ENABLE" action: "NETWORK_NIC_ENABLE"
options: options:
node_id: 5 node_id: 4
nic_id: 2 nic_id: 2
50: 50:
action: "NETWORK_NIC_DISABLE" action: "NETWORK_NIC_DISABLE"
options: options:
node_id: 6 node_id: 5
nic_id: 1 nic_id: 1
51: 51:
action: "NETWORK_NIC_ENABLE" action: "NETWORK_NIC_ENABLE"
options: options:
node_id: 6 node_id: 5
nic_id: 1 nic_id: 1
52: 52:
action: "NETWORK_NIC_DISABLE" action: "NETWORK_NIC_DISABLE"
options: options:
node_id: 7 node_id: 6
nic_id: 1 nic_id: 1
53: 53:
action: "NETWORK_NIC_ENABLE" action: "NETWORK_NIC_ENABLE"
options: options:
node_id: 7 node_id: 6
nic_id: 1 nic_id: 1
options: options:
nodes: nodes:
- node_ref: router_1
- node_ref: switch_1
- node_ref: switch_2
- node_ref: domain_controller - node_ref: domain_controller
- node_ref: web_server - node_ref: web_server
services:
- service_ref: web_server_web_service
- node_ref: database_server - node_ref: database_server
services:
- service_ref: database_service
- node_ref: backup_server - node_ref: backup_server
- node_ref: security_suite - node_ref: security_suite
- node_ref: client_1 - node_ref: client_1

View File

@@ -89,7 +89,7 @@ class NodeServiceAbstractAction(AbstractAction):
service_uuid = self.manager.get_service_uuid_by_idx(node_id, service_id) service_uuid = self.manager.get_service_uuid_by_idx(node_id, service_id)
if node_uuid is None or service_uuid is None: if node_uuid is None or service_uuid is None:
return ["do_nothing"] return ["do_nothing"]
return ["network", "node", node_uuid, "services", service_uuid, self.verb] return ["network", "node", node_uuid, "service", service_uuid, self.verb]
class NodeServiceScanAction(NodeServiceAbstractAction): class NodeServiceScanAction(NodeServiceAbstractAction):
@@ -156,6 +156,14 @@ class NodeServiceEnableAction(NodeServiceAbstractAction):
self.verb: str = "enable" self.verb: str = "enable"
class NodeServicePatchAction(NodeServiceAbstractAction):
"""Action which patches a service."""
def __init__(self, manager: "ActionManager", num_nodes: int, num_services: int, **kwargs) -> None:
super().__init__(manager=manager, num_nodes=num_nodes, num_services=num_services)
self.verb: str = "patch"
class NodeApplicationAbstractAction(AbstractAction): class NodeApplicationAbstractAction(AbstractAction):
""" """
Base class for application actions. Base class for application actions.
@@ -262,7 +270,7 @@ class NodeFileAbstractAction(AbstractAction):
file_uuid = self.manager.get_file_uuid_by_idx(node_idx=node_id, folder_idx=folder_id, file_idx=file_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: 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", "folder", folder_uuid, "files", file_uuid, self.verb] return ["network", "node", node_uuid, "file_system", "folder", folder_uuid, "file", file_uuid, self.verb]
class NodeFileScanAction(NodeFileAbstractAction): class NodeFileScanAction(NodeFileAbstractAction):
@@ -566,6 +574,7 @@ class ActionManager:
"NODE_SERVICE_RESTART": NodeServiceRestartAction, "NODE_SERVICE_RESTART": NodeServiceRestartAction,
"NODE_SERVICE_DISABLE": NodeServiceDisableAction, "NODE_SERVICE_DISABLE": NodeServiceDisableAction,
"NODE_SERVICE_ENABLE": NodeServiceEnableAction, "NODE_SERVICE_ENABLE": NodeServiceEnableAction,
"NODE_SERVICE_PATCH": NodeServicePatchAction,
"NODE_APPLICATION_EXECUTE": NodeApplicationExecuteAction, "NODE_APPLICATION_EXECUTE": NodeApplicationExecuteAction,
"NODE_FILE_SCAN": NodeFileScanAction, "NODE_FILE_SCAN": NodeFileScanAction,
"NODE_FILE_CHECKHASH": NodeFileCheckhashAction, "NODE_FILE_CHECKHASH": NodeFileCheckhashAction,
@@ -594,6 +603,7 @@ class ActionManager:
actions: List[str], # stores list of actions available to agent actions: List[str], # stores list of actions available to agent
node_uuids: List[str], # allows mapping index to node node_uuids: List[str], # allows mapping index to node
application_uuids: List[List[str]], # allows mapping index to application application_uuids: List[List[str]], # allows mapping index to application
service_uuids: List[List[str]], # allows mapping index to service
max_folders_per_node: int = 2, # allows calculating shape max_folders_per_node: int = 2, # allows calculating shape
max_files_per_folder: int = 2, # allows calculating shape max_files_per_folder: int = 2, # allows calculating shape
max_services_per_node: int = 2, # allows calculating shape max_services_per_node: int = 2, # allows calculating shape
@@ -635,6 +645,7 @@ class ActionManager:
self.game: "PrimaiteGame" = game self.game: "PrimaiteGame" = game
self.node_uuids: List[str] = node_uuids self.node_uuids: List[str] = node_uuids
self.application_uuids: List[List[str]] = application_uuids self.application_uuids: List[List[str]] = application_uuids
self.service_uuids: List[List[str]] = service_uuids
self.protocols: List[str] = protocols self.protocols: List[str] = protocols
self.ports: List[str] = ports self.ports: List[str] = ports
@@ -804,6 +815,11 @@ class ActionManager:
:return: The UUID of the service. Or None if the node has fewer services than the given index. :return: The UUID of the service. Or None if the node has fewer services than the given index.
:rtype: Optional[str] :rtype: Optional[str]
""" """
# if a mapping was specified, use that mapping, otherwise just use the list of all installed services
if self.service_uuids:
if self.service_uuids[node_idx]:
return self.service_uuids[node_idx][service_idx]
node_uuid = self.get_node_uuid_by_idx(node_idx) node_uuid = self.get_node_uuid_by_idx(node_idx)
node = self.game.simulation.network.nodes[node_uuid] node = self.game.simulation.network.nodes[node_uuid]
service_uuids = list(node.services.keys()) service_uuids = list(node.services.keys())

View File

@@ -361,6 +361,7 @@ class PrimaiteGame:
# CREATE ACTION SPACE # CREATE ACTION SPACE
action_space_cfg["options"]["node_uuids"] = [] action_space_cfg["options"]["node_uuids"] = []
action_space_cfg["options"]["application_uuids"] = [] action_space_cfg["options"]["application_uuids"] = []
action_space_cfg["options"]["service_uuids"] = []
# if a list of nodes is defined, convert them from node references to node UUIDs # if a list of nodes is defined, convert them from node references to node UUIDs
for action_node_option in action_space_cfg.get("options", {}).pop("nodes", {}): for action_node_option in action_space_cfg.get("options", {}).pop("nodes", {}):
@@ -375,10 +376,21 @@ class PrimaiteGame:
# node_uuid, whereas here the application gets added by uuid. # node_uuid, whereas here the application gets added by uuid.
application_uuid = game.ref_map_applications[application_option["application_ref"]] application_uuid = game.ref_map_applications[application_option["application_ref"]]
node_application_uuids.append(application_uuid) node_application_uuids.append(application_uuid)
action_space_cfg["options"]["application_uuids"].append(node_application_uuids) action_space_cfg["options"]["application_uuids"].append(node_application_uuids)
else: else:
action_space_cfg["options"]["application_uuids"].append([]) action_space_cfg["options"]["application_uuids"].append([])
if "services" in action_node_option:
node_service_uuids = []
for service_option in action_node_option["services"]:
service_uuid = game.ref_map_services[service_option["service_ref"]]
node_service_uuids.append(service_uuid)
action_space_cfg["options"]["service_uuids"].append(node_service_uuids)
else:
action_space_cfg["options"]["service_uuids"].append([])
# Each action space can potentially have a different list of nodes that it can apply to. Therefore, # Each action space can potentially have a different list of nodes that it can apply to. Therefore,
# we will pass node_uuids as a part of the action space config. # we will pass node_uuids as a part of the action space config.
# However, it's not possible to specify the node uuids directly in the config, as they are generated # However, it's not possible to specify the node uuids directly in the config, as they are generated

View File

@@ -102,6 +102,11 @@ class Folder(FileSystemItemABC):
name="delete", name="delete",
request_type=RequestType(func=lambda request, context: self.remove_file_by_id(file_uuid=request[0])), request_type=RequestType(func=lambda request, context: self.remove_file_by_id(file_uuid=request[0])),
) )
self._file_request_manager = RequestManager()
rm.add_request(
name="file",
request_type=RequestType(func=lambda request, context: self._file_request_manager),
)
return rm return rm
def describe_state(self) -> Dict: def describe_state(self) -> Dict:
@@ -254,6 +259,7 @@ class Folder(FileSystemItemABC):
# add to list # add to list
self.files[file.uuid] = file self.files[file.uuid] = file
self._files_by_name[file.name] = file self._files_by_name[file.name] = file
self._file_request_manager.add_request(file.uuid, RequestType(func=file._request_manager))
file.folder = self file.folder = self
def remove_file(self, file: Optional[File]): def remove_file(self, file: Optional[File]):
@@ -273,6 +279,7 @@ class Folder(FileSystemItemABC):
self.deleted_files[file.uuid] = file self.deleted_files[file.uuid] = file
file.delete() file.delete()
self.sys_log.info(f"Removed file {file.name} (id: {file.uuid})") self.sys_log.info(f"Removed file {file.name} (id: {file.uuid})")
self._file_request_manager.remove_request(file.uuid)
else: else:
_LOGGER.debug(f"File with UUID {file.uuid} was not found.") _LOGGER.debug(f"File with UUID {file.uuid} was not found.")

View File

@@ -55,3 +55,9 @@ class Simulation(SimComponent):
} }
) )
return state return state
def apply_timestep(self, timestep: int) -> None:
"""Apply a timestep to the simulation."""
super().apply_timestep(timestep)
self.network.apply_timestep(timestep)
# self.domain.apply_timestep(timestep)

View File

@@ -89,6 +89,10 @@ class Software(SimComponent):
"The FileSystem of the Node the Software is installed on." "The FileSystem of the Node the Software is installed on."
folder: Optional[Folder] = None folder: Optional[Folder] = None
"The folder on the file system the Software uses." "The folder on the file system the Software uses."
patching_duration: int = 2
"The number of ticks it takes to patch the software."
_patching_countdown: Optional[int] = None
"Current number of ticks left to patch the software."
def set_original_state(self): def set_original_state(self):
"""Sets the original state.""" """Sets the original state."""
@@ -111,6 +115,12 @@ class Software(SimComponent):
func=lambda request, context: self.set_health_state(SoftwareHealthState.COMPROMISED), func=lambda request, context: self.set_health_state(SoftwareHealthState.COMPROMISED),
), ),
) )
rm.add_request(
"patch",
RequestType(
func=lambda request, context: self.patch(),
),
)
rm.add_request("scan", RequestType(func=lambda request, context: self.scan())) rm.add_request("scan", RequestType(func=lambda request, context: self.scan()))
return rm return rm
@@ -181,10 +191,33 @@ class Software(SimComponent):
"""Update the observed health status to match the actual health status.""" """Update the observed health status to match the actual health status."""
self.health_state_visible = self.health_state_actual self.health_state_visible = self.health_state_actual
def patch(self) -> None:
"""Perform a patch on the software."""
self._patching_countdown = self.patching_duration
self.set_health_state(SoftwareHealthState.PATCHING)
def _update_patch_status(self) -> None:
"""Update the patch status of the software."""
self._patching_countdown -= 1
if self._patching_countdown <= 0:
self.set_health_state(SoftwareHealthState.GOOD)
self._patching_countdown = None
self.patching_count += 1
def reveal_to_red(self) -> None: def reveal_to_red(self) -> None:
"""Reveals the software to the red agent.""" """Reveals the software to the red agent."""
self.revealed_to_red = True self.revealed_to_red = True
def apply_timestep(self, timestep: int) -> None:
"""
Apply a single timestep to the software.
:param timestep: The current timestep of the simulation.
"""
super().apply_timestep(timestep)
if self.health_state_actual == SoftwareHealthState.PATCHING:
self._update_patch_status()
class IOSoftware(Software): class IOSoftware(Software):
""" """