Merge branch '4.0.0a1-dev' into feature/3075_Migrate_notebooks_to_MilPac_(Core_changes)

This commit is contained in:
Nick Todd
2025-02-18 11:48:26 +00:00
51 changed files with 171 additions and 75 deletions

View File

@@ -1,3 +1,6 @@
metadata:
version: 3.0
game: game:
ports: ports:
- ARP - ARP

View File

@@ -1,3 +1,6 @@
metadata:
version: 3.0
game: game:
ports: ports:
- ARP - ARP

View File

@@ -1,3 +1,6 @@
metadata:
version: 3.0
io_settings: io_settings:
save_agent_actions: true save_agent_actions: true
save_step_metadata: false save_step_metadata: false

View File

@@ -1,3 +1,6 @@
metadata:
version: 3.0
io_settings: io_settings:
save_agent_actions: true save_agent_actions: true
save_step_metadata: false save_step_metadata: false

View File

@@ -1,3 +1,6 @@
metadata:
version: 3.0
game: game:
max_episode_length: 128 max_episode_length: 128
ports: [] ports: []

View File

@@ -1,3 +1,6 @@
metadata:
version: 3.0
game: game:
ports: ports:
- ARP - ARP

View File

@@ -1,3 +1,6 @@
metadata:
version: 3.0
io_settings: io_settings:
save_agent_actions: true save_agent_actions: true
save_step_metadata: false save_step_metadata: false

View File

@@ -70,7 +70,7 @@ class AbstractAgent(BaseModel, ABC):
config: ConfigSchema = Field(default_factory=lambda: AbstractAgent.ConfigSchema()) config: ConfigSchema = Field(default_factory=lambda: AbstractAgent.ConfigSchema())
logger: AgentLog = AgentLog(agent_name="Abstract_Agent") logger: AgentLog = None
history: List[AgentHistoryItem] = [] history: List[AgentHistoryItem] = []
action_manager: ActionManager = Field(default_factory=lambda: ActionManager()) action_manager: ActionManager = Field(default_factory=lambda: ActionManager())
@@ -79,6 +79,11 @@ class AbstractAgent(BaseModel, ABC):
_registry: ClassVar[Dict[str, Type[AbstractAgent]]] = {} _registry: ClassVar[Dict[str, Type[AbstractAgent]]] = {}
def __init__(self, **kwargs):
"""Initialise and setup agent logger."""
super().__init__(**kwargs)
self.logger: AgentLog = AgentLog(agent_name=kwargs["config"]["ref"])
def __init_subclass__(cls, discriminator: Optional[str] = None, **kwargs: Any) -> None: def __init_subclass__(cls, discriminator: Optional[str] = None, **kwargs: Any) -> None:
super().__init_subclass__(**kwargs) super().__init_subclass__(**kwargs)
if discriminator is None: if discriminator is None:

View File

@@ -449,10 +449,8 @@ class IPWirelessNetworkInterface(WirelessNetworkInterface, Layer3Interface, ABC)
`default_gateway_hello` method is not defined, ignoring such errors to proceed without interruption. `default_gateway_hello` method is not defined, ignoring such errors to proceed without interruption.
""" """
super().enable() super().enable()
try: if hasattr(self._connected_node, "default_gateway_hello"):
self._connected_node.default_gateway_hello() self._connected_node.default_gateway_hello()
except AttributeError:
pass
@abstractmethod @abstractmethod
def receive_frame(self, frame: Frame) -> bool: def receive_frame(self, frame: Frame) -> bool:

View File

@@ -163,16 +163,6 @@ class Network(SimComponent):
:param links: Include link details in the output. Defaults to True. :param links: Include link details in the output. Defaults to True.
:param markdown: Use Markdown style in table output. Defaults to False. :param markdown: Use Markdown style in table output. Defaults to False.
""" """
nodes_type_map = {
"Router": self.router_nodes,
"Firewall": self.firewall_nodes,
"Switch": self.switch_nodes,
"Server": self.server_nodes,
"Computer": self.computer_nodes,
"Printer": self.printer_nodes,
"Wireless Router": self.wireless_router_nodes,
}
if nodes: if nodes:
table = PrettyTable(["Node", "Type", "Operating State"]) table = PrettyTable(["Node", "Type", "Operating State"])
if markdown: if markdown:
@@ -189,8 +179,7 @@ class Network(SimComponent):
table.set_style(MARKDOWN) table.set_style(MARKDOWN)
table.align = "l" table.align = "l"
table.title = "IP Addresses" table.title = "IP Addresses"
for nodes in nodes_type_map.values(): for node in self.nodes.values():
for node in nodes:
for i, port in node.network_interface.items(): for i, port in node.network_interface.items():
if hasattr(port, "ip_address"): if hasattr(port, "ip_address"):
if port.ip_address != IPv4Address("127.0.0.1"): if port.ip_address != IPv4Address("127.0.0.1"):
@@ -215,8 +204,7 @@ class Network(SimComponent):
table.align = "l" table.align = "l"
table.title = "Links" table.title = "Links"
links = list(self.links.values()) links = list(self.links.values())
for nodes in nodes_type_map.values(): for node in self.nodes.values():
for node in nodes:
for link in links[::-1]: for link in links[::-1]:
if node in [link.endpoint_a.parent, link.endpoint_b.parent]: if node in [link.endpoint_a.parent, link.endpoint_b.parent]:
table.add_row( table.add_row(

View File

@@ -155,7 +155,12 @@ class OfficeLANAdder(NetworkNodeAdder, discriminator="office-lan"):
# Create a core switch if more than one edge switch is needed # Create a core switch if more than one edge switch is needed
if num_of_switches > 1: if num_of_switches > 1:
core_switch = Switch.from_config( core_switch = Switch.from_config(
config={"type": "switch", "hostname": f"switch_core_{config.lan_name}", "start_up_duration": 0} config={
"type": "switch",
"hostname": f"switch_core_{config.lan_name}",
"start_up_duration": 0,
"num_ports": 24,
}
) )
core_switch.power_on() core_switch.power_on()
network.add_node(core_switch) network.add_node(core_switch)
@@ -183,7 +188,12 @@ class OfficeLANAdder(NetworkNodeAdder, discriminator="office-lan"):
switch_port = 0 switch_port = 0
switch_n = 1 switch_n = 1
switch = Switch.from_config( switch = Switch.from_config(
config={"type": "switch", "hostname": f"switch_edge_{switch_n}_{config.lan_name}", "start_up_duration": 0} config={
"type": "switch",
"hostname": f"switch_edge_{switch_n}_{config.lan_name}",
"start_up_duration": 0,
"num_ports": 24,
}
) )
switch.power_on() switch.power_on()
network.add_node(switch) network.add_node(switch)
@@ -207,6 +217,7 @@ class OfficeLANAdder(NetworkNodeAdder, discriminator="office-lan"):
"type": "switch", "type": "switch",
"hostname": f"switch_edge_{switch_n}_{config.lan_name}", "hostname": f"switch_edge_{switch_n}_{config.lan_name}",
"start_up_duration": 0, "start_up_duration": 0,
"num_ports": 24,
} }
) )
switch.power_on() switch.power_on()

View File

@@ -639,10 +639,8 @@ class IPWiredNetworkInterface(WiredNetworkInterface, Layer3Interface, ABC):
`default_gateway_hello` method is not defined, ignoring such errors to proceed without interruption. `default_gateway_hello` method is not defined, ignoring such errors to proceed without interruption.
""" """
super().enable() super().enable()
try: if hasattr(self._connected_node, "default_gateway_hello"):
self._connected_node.default_gateway_hello() self._connected_node.default_gateway_hello()
except AttributeError:
pass
return True return True
@abstractmethod @abstractmethod

View File

@@ -333,7 +333,7 @@ class HostNode(Node, discriminator="host-node"):
class ConfigSchema(Node.ConfigSchema): class ConfigSchema(Node.ConfigSchema):
"""Configuration Schema for HostNode class.""" """Configuration Schema for HostNode class."""
type: Literal["host-node"] type: Literal["host-node"] = "host-node"
hostname: str = "HostNode" hostname: str = "HostNode"
subnet_mask: IPV4Address = "255.255.255.0" subnet_mask: IPV4Address = "255.255.255.0"
ip_address: IPV4Address ip_address: IPV4Address
@@ -375,7 +375,7 @@ class HostNode(Node, discriminator="host-node"):
This method is invoked to ensure the host node can communicate with its default gateway, primarily to confirm This method is invoked to ensure the host node can communicate with its default gateway, primarily to confirm
network connectivity and populate the ARP cache with the gateway's MAC address. network connectivity and populate the ARP cache with the gateway's MAC address.
""" """
if self.operating_state == NodeOperatingState.ON and self.default_gateway: if self.operating_state == NodeOperatingState.ON and self.config.default_gateway:
self.software_manager.arp.get_default_gateway_mac_address() self.software_manager.arp.get_default_gateway_mac_address()
def receive_frame(self, frame: Frame, from_network_interface: NIC): def receive_frame(self, frame: Frame, from_network_interface: NIC):

View File

@@ -1351,7 +1351,6 @@ class Router(NetworkNode, discriminator="router"):
:return: A dictionary representing the current state. :return: A dictionary representing the current state.
""" """
state = super().describe_state() state = super().describe_state()
state["num_ports"] = self.config.num_ports
state["acl"] = self.acl.describe_state() state["acl"] = self.acl.describe_state()
return state return state

View File

@@ -103,8 +103,8 @@ class Switch(NetworkNode, discriminator="switch"):
type: Literal["switch"] = "switch" type: Literal["switch"] = "switch"
hostname: str = "Switch" hostname: str = "Switch"
num_ports: int = 24 num_ports: int = 8
"The number of ports on the switch. Default is 24." "The number of ports on the switch."
config: ConfigSchema = Field(default_factory=lambda: Switch.ConfigSchema()) config: ConfigSchema = Field(default_factory=lambda: Switch.ConfigSchema())
@@ -139,7 +139,6 @@ class Switch(NetworkNode, discriminator="switch"):
""" """
state = super().describe_state() state = super().describe_state()
state["ports"] = {port_num: port.describe_state() for port_num, port in self.network_interface.items()} state["ports"] = {port_num: port.describe_state() for port_num, port in self.network_interface.items()}
state["num_ports"] = self.config.num_ports # redundant?
state["mac_address_table"] = {mac: port.port_num for mac, port in self.mac_address_table.items()} state["mac_address_table"] = {mac: port.port_num for mac, port in self.mac_address_table.items()}
return state return state

View File

@@ -137,8 +137,8 @@ class ARP(Service, discriminator="arp"):
break break
if use_default_gateway: if use_default_gateway:
if self.software_manager.node.default_gateway: if self.software_manager.node.config.default_gateway:
target_ip_address = self.software_manager.node.default_gateway target_ip_address = self.software_manager.node.config.default_gateway
else: else:
return return

View File

@@ -82,7 +82,7 @@ class Software(SimComponent, ABC):
"""Configurable options for all software.""" """Configurable options for all software."""
model_config = ConfigDict(extra="forbid") model_config = ConfigDict(extra="forbid")
starting_health_state: SoftwareHealthState = SoftwareHealthState.UNUSED starting_health_state: SoftwareHealthState = SoftwareHealthState.GOOD
criticality: SoftwareCriticality = SoftwareCriticality.LOWEST criticality: SoftwareCriticality = SoftwareCriticality.LOWEST
fixing_duration: int = 2 fixing_duration: int = 2

View File

@@ -1,3 +1,6 @@
metadata:
version: 3.0
io_settings: io_settings:
save_agent_actions: false save_agent_actions: false
save_step_metadata: false save_step_metadata: false

View File

@@ -1,3 +1,6 @@
metadata:
version: 3.0
game: game:
ports: ports:
- ARP - ARP

View File

@@ -4,6 +4,9 @@
# | node_a |------| switch_1 |------| node_b | # | node_a |------| switch_1 |------| node_b |
# -------------- -------------- -------------- # -------------- -------------- --------------
# #
metadata:
version: 3.0
io_settings: io_settings:
save_step_metadata: false save_step_metadata: false
save_pcap_logs: true save_pcap_logs: true

View File

@@ -4,6 +4,8 @@
# | client_1 |------| switch_1 |------| client_2 | # | client_1 |------| switch_1 |------| client_2 |
# -------------- -------------- -------------- # -------------- -------------- --------------
# #
metadata:
version: 3.0
io_settings: io_settings:
save_step_metadata: false save_step_metadata: false

View File

@@ -1,3 +1,6 @@
metadata:
version: 3.0
io_settings: io_settings:
save_step_metadata: false save_step_metadata: false
save_pcap_logs: true save_pcap_logs: true

View File

@@ -1,3 +1,6 @@
metadata:
version: 3.0
io_settings: io_settings:
save_step_metadata: false save_step_metadata: false
save_pcap_logs: true save_pcap_logs: true

View File

@@ -4,6 +4,9 @@
# | client_1 |------| switch_1 |------| client_2 | # | client_1 |------| switch_1 |------| client_2 |
# -------------- -------------- -------------- # -------------- -------------- --------------
# #
metadata:
version: 3.0
io_settings: io_settings:
save_step_metadata: false save_step_metadata: false
save_pcap_logs: true save_pcap_logs: true

View File

@@ -1,3 +1,6 @@
metadata:
version: 3.0
io_settings: io_settings:
save_agent_actions: true save_agent_actions: true
save_step_metadata: false save_step_metadata: false

View File

@@ -30,6 +30,9 @@
# | external_computer |------| switch_3 |------| external_server | # | external_computer |------| switch_3 |------| external_server |
# ----------------------- -------------- --------------------- # ----------------------- -------------- ---------------------
# #
metadata:
version: 3.0
io_settings: io_settings:
save_step_metadata: false save_step_metadata: false
save_pcap_logs: true save_pcap_logs: true

View File

@@ -1,3 +1,6 @@
metadata:
version: 3.0
game: game:
ports: ports:
- ARP - ARP

View File

@@ -1,3 +1,6 @@
metadata:
version: 3.0
io_settings: io_settings:
save_agent_actions: true save_agent_actions: true
save_step_metadata: false save_step_metadata: false

View File

@@ -30,6 +30,9 @@
# | external_computer |------| switch_3 |------| external_server | # | external_computer |------| switch_3 |------| external_server |
# ----------------------- -------------- --------------------- # ----------------------- -------------- ---------------------
# #
metadata:
version: 3.0
io_settings: io_settings:
save_step_metadata: false save_step_metadata: false
save_pcap_logs: true save_pcap_logs: true

View File

@@ -4,6 +4,9 @@
# | client_1 |------| switch_1 |------| client_2 | # | client_1 |------| switch_1 |------| client_2 |
# -------------- -------------- -------------- # -------------- -------------- --------------
# #
metadata:
version: 3.0
io_settings: io_settings:
save_step_metadata: false save_step_metadata: false
save_pcap_logs: true save_pcap_logs: true

View File

@@ -1,3 +1,6 @@
metadata:
version: 3.0
io_settings: io_settings:
save_step_metadata: false save_step_metadata: false
save_pcap_logs: false save_pcap_logs: false

View File

@@ -1,3 +1,6 @@
metadata:
version: 3.0
io_settings: io_settings:
save_agent_actions: false save_agent_actions: false
save_step_metadata: false save_step_metadata: false

View File

@@ -1,3 +1,6 @@
metadata:
version: 3.0
io_settings: io_settings:
save_step_metadata: false save_step_metadata: false
save_pcap_logs: true save_pcap_logs: true

View File

@@ -1,3 +1,6 @@
metadata:
version: 3.0
io_settings: io_settings:
save_step_metadata: false save_step_metadata: false
save_pcap_logs: true save_pcap_logs: true

View File

@@ -1,3 +1,6 @@
metadata:
version: 3.0
io_settings: io_settings:
save_step_metadata: false save_step_metadata: false
save_pcap_logs: true save_pcap_logs: true

View File

@@ -1,3 +1,6 @@
metadata:
version: 3.0
io_settings: io_settings:
save_step_metadata: false save_step_metadata: false
save_pcap_logs: true save_pcap_logs: true

View File

@@ -1,3 +1,6 @@
metadata:
version: 3.0
io_settings: io_settings:
save_agent_actions: true save_agent_actions: true
save_step_metadata: false save_step_metadata: false

View File

@@ -1,3 +1,6 @@
metadata:
version: 3.0
io_settings: io_settings:
save_agent_actions: false save_agent_actions: false
save_step_metadata: false save_step_metadata: false

View File

@@ -4,6 +4,9 @@
# | client_1 |------| switch_1 |------| client_2 | # | client_1 |------| switch_1 |------| client_2 |
# -------------- -------------- -------------- # -------------- -------------- --------------
# #
metadata:
version: 3.0
io_settings: io_settings:
save_step_metadata: false save_step_metadata: false
save_pcap_logs: true save_pcap_logs: true

View File

@@ -1,3 +1,6 @@
metadata:
version: 3.0
io_settings: io_settings:
save_agent_actions: true save_agent_actions: true
save_step_metadata: false save_step_metadata: false

View File

@@ -1,3 +1,6 @@
metadata:
version: 3.0
io_settings: io_settings:
save_agent_actions: true save_agent_actions: true
save_step_metadata: true save_step_metadata: true

View File

@@ -1,3 +1,6 @@
metadata:
version: 3.0
game: game:
max_episode_length: 256 max_episode_length: 256
ports: ports:

View File

@@ -1,3 +1,6 @@
metadata:
version: 3.0
game: game:
max_episode_length: 256 max_episode_length: 256
ports: ports:

View File

@@ -1,3 +1,6 @@
metadata:
version: 3.0
game: game:
max_episode_length: 256 max_episode_length: 256
ports: ports:

View File

@@ -49,7 +49,7 @@ class GigaSwitch(NetworkNode, discriminator="gigaswitch"):
if markdown: if markdown:
table.set_style(MARKDOWN) table.set_style(MARKDOWN)
table.align = "l" table.align = "l"
table.title = f"{self.hostname} Switch Ports" table.title = f"{self.config.hostname} Switch Ports"
for port_num, port in self.network_interface.items(): for port_num, port in self.network_interface.items():
table.add_row([port_num, port.mac_address, port.speed, "Enabled" if port.enabled else "Disabled"]) table.add_row([port_num, port.mac_address, port.speed, "Enabled" if port.enabled else "Disabled"])
print(table) print(table)

View File

@@ -106,7 +106,6 @@ def test_remote_login_change_password(game_and_agent_fixture: Tuple[PrimaiteGame
"username": "user123", "username": "user123",
"current_password": "password", "current_password": "password",
"new_password": "different_password", "new_password": "different_password",
# "remote_ip": str(server_1.network_interface[1].ip_address),
}, },
) )
agent.store_action(action) agent.store_action(action)
@@ -146,7 +145,6 @@ def test_change_password_logs_out_user(game_and_agent_fixture: Tuple[PrimaiteGam
"username": "user123", "username": "user123",
"current_password": "password", "current_password": "password",
"new_password": "different_password", "new_password": "different_password",
# "remote_ip": str(server_1.network_interface[1].ip_address),
}, },
) )
agent.store_action(action) agent.store_action(action)

View File

@@ -4,7 +4,7 @@ from primaite.game.agent.scripted_agents.random_agent import RandomAgent
def test_creating_empty_agent(): def test_creating_empty_agent():
agent = RandomAgent() agent = RandomAgent(config={"ref": "Empty Agent"})
assert len(agent.action_manager.action_map) == 0 assert len(agent.action_manager.action_map) == 0
assert isinstance(agent.observation_manager.obs, NullObservation) assert isinstance(agent.observation_manager.obs, NullObservation)
assert len(agent.reward_function.reward_components) == 0 assert len(agent.reward_function.reward_components) == 0

View File

@@ -17,4 +17,3 @@ def switch() -> Switch:
def test_describe_state(switch): def test_describe_state(switch):
state = switch.describe_state() state = switch.describe_state()
assert len(state.get("ports")) is 8 assert len(state.get("ports")) is 8
assert state.get("num_ports") is 8

View File

@@ -18,7 +18,7 @@ def test_scan(application):
def test_run_application(application): def test_run_application(application):
assert application.operating_state == ApplicationOperatingState.CLOSED assert application.operating_state == ApplicationOperatingState.CLOSED
assert application.health_state_actual == SoftwareHealthState.UNUSED assert application.health_state_actual == SoftwareHealthState.GOOD
application.run() application.run()
assert application.operating_state == ApplicationOperatingState.RUNNING assert application.operating_state == ApplicationOperatingState.RUNNING
@@ -37,9 +37,9 @@ def test_close_application(application):
def test_application_describe_states(application): def test_application_describe_states(application):
assert application.operating_state == ApplicationOperatingState.CLOSED assert application.operating_state == ApplicationOperatingState.CLOSED
assert application.health_state_actual == SoftwareHealthState.UNUSED assert application.health_state_actual == SoftwareHealthState.GOOD
assert SoftwareHealthState.UNUSED.value == application.describe_state().get("health_state_actual") assert SoftwareHealthState.GOOD.value == application.describe_state().get("health_state_actual")
application.run() application.run()
assert SoftwareHealthState.GOOD.value == application.describe_state().get("health_state_actual") assert SoftwareHealthState.GOOD.value == application.describe_state().get("health_state_actual")

View File

@@ -22,7 +22,7 @@ def test_scan(service):
def test_start_service(service): def test_start_service(service):
assert service.operating_state == ServiceOperatingState.STOPPED assert service.operating_state == ServiceOperatingState.STOPPED
assert service.health_state_actual == SoftwareHealthState.UNUSED assert service.health_state_actual == SoftwareHealthState.GOOD
service.start() service.start()
assert service.operating_state == ServiceOperatingState.RUNNING assert service.operating_state == ServiceOperatingState.RUNNING
@@ -43,7 +43,7 @@ def test_pause_and_resume_service(service):
assert service.operating_state == ServiceOperatingState.STOPPED assert service.operating_state == ServiceOperatingState.STOPPED
service.resume() service.resume()
assert service.operating_state == ServiceOperatingState.STOPPED assert service.operating_state == ServiceOperatingState.STOPPED
assert service.health_state_actual == SoftwareHealthState.UNUSED assert service.health_state_actual == SoftwareHealthState.GOOD
service.start() service.start()
assert service.health_state_actual == SoftwareHealthState.GOOD assert service.health_state_actual == SoftwareHealthState.GOOD
@@ -58,11 +58,11 @@ def test_pause_and_resume_service(service):
def test_restart(service): def test_restart(service):
assert service.operating_state == ServiceOperatingState.STOPPED assert service.operating_state == ServiceOperatingState.STOPPED
assert service.health_state_actual == SoftwareHealthState.UNUSED assert service.health_state_actual == SoftwareHealthState.GOOD
service.restart() service.restart()
# Service is STOPPED. Restart will only work if the service was PAUSED or RUNNING # Service is STOPPED. Restart will only work if the service was PAUSED or RUNNING
assert service.operating_state == ServiceOperatingState.STOPPED assert service.operating_state == ServiceOperatingState.STOPPED
assert service.health_state_actual == SoftwareHealthState.UNUSED assert service.health_state_actual == SoftwareHealthState.GOOD
service.start() service.start()
assert service.operating_state == ServiceOperatingState.RUNNING assert service.operating_state == ServiceOperatingState.RUNNING
@@ -157,11 +157,11 @@ def test_service_fixing(service):
def test_enable_disable(service): def test_enable_disable(service):
service.disable() service.disable()
assert service.operating_state == ServiceOperatingState.DISABLED assert service.operating_state == ServiceOperatingState.DISABLED
assert service.health_state_actual == SoftwareHealthState.UNUSED assert service.health_state_actual == SoftwareHealthState.GOOD
service.enable() service.enable()
assert service.operating_state == ServiceOperatingState.STOPPED assert service.operating_state == ServiceOperatingState.STOPPED
assert service.health_state_actual == SoftwareHealthState.UNUSED assert service.health_state_actual == SoftwareHealthState.GOOD
def test_overwhelm_service(service): def test_overwhelm_service(service):

View File

@@ -39,6 +39,6 @@ def test_software_creation(software):
def test_software_set_health_state(software): def test_software_set_health_state(software):
assert software.health_state_actual == SoftwareHealthState.UNUSED
software.set_health_state(SoftwareHealthState.GOOD)
assert software.health_state_actual == SoftwareHealthState.GOOD assert software.health_state_actual == SoftwareHealthState.GOOD
software.set_health_state(SoftwareHealthState.COMPROMISED)
assert software.health_state_actual == SoftwareHealthState.COMPROMISED