3086 UC7 Migration - All YAMLS, tests and notebooks. A few lingering issues such as the OS-SCAN not working and agent logs not appearing.
This commit is contained in:
173
tests/e2e_integration_tests/test_uc7_agents.py
Normal file
173
tests/e2e_integration_tests/test_uc7_agents.py
Normal file
@@ -0,0 +1,173 @@
|
||||
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
from primaite.config.load import _EXAMPLE_CFG, load
|
||||
from primaite.game.game import PrimaiteGame
|
||||
from primaite.session.environment import PrimaiteGymEnv
|
||||
from primaite.simulator.file_system.file import File
|
||||
from primaite.simulator.file_system.file_system_item_abc import FileSystemItemHealthStatus
|
||||
from primaite.simulator.network.container import Network
|
||||
from primaite.simulator.network.hardware.nodes.host.computer import Computer
|
||||
from primaite.simulator.network.hardware.nodes.host.server import Server
|
||||
from primaite.simulator.network.hardware.nodes.network.firewall import Firewall
|
||||
from primaite.simulator.system.applications.application import ApplicationOperatingState
|
||||
from primaite.simulator.system.applications.database_client import DatabaseClient
|
||||
from primaite.simulator.system.applications.red_applications.c2.c2_beacon import C2Beacon
|
||||
from primaite.simulator.system.applications.red_applications.ransomware_script import RansomwareScript
|
||||
from primaite.simulator.system.applications.web_browser import WebBrowser
|
||||
from primaite.simulator.system.services.database.database_service import DatabaseService
|
||||
from primaite.simulator.system.services.dns.dns_client import DNSClient
|
||||
from primaite.simulator.system.services.dns.dns_server import DNSServer
|
||||
from primaite.simulator.system.services.ftp.ftp_client import FTPClient
|
||||
from primaite.simulator.system.services.ftp.ftp_server import FTPServer
|
||||
from primaite.simulator.system.services.ntp.ntp_client import NTPClient
|
||||
from primaite.simulator.system.services.ntp.ntp_server import NTPServer
|
||||
from primaite.simulator.system.services.service import ServiceOperatingState
|
||||
from primaite.simulator.system.software import SoftwareHealthState
|
||||
|
||||
CONFIG_FILE = _EXAMPLE_CFG / "uc7_config.yaml"
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def uc7_environment() -> PrimaiteGymEnv:
|
||||
with open(_EXAMPLE_CFG / "uc7_config.yaml", mode="r") as uc7_config:
|
||||
cfg = yaml.safe_load(uc7_config)
|
||||
env = PrimaiteGymEnv(env_config=cfg)
|
||||
return env
|
||||
|
||||
|
||||
def assert_agent_reward(env: PrimaiteGymEnv, agent_name: str, positive: bool):
|
||||
"""Asserts that a given agent has a reward that is below/above or equal to 0 dependant on arguments."""
|
||||
agent_reward = env.game.agents[agent_name].reward_function.total_reward
|
||||
if agent_name == "defender":
|
||||
return # ignore blue agent
|
||||
if positive is True:
|
||||
assert agent_reward >= 0 # Asserts that no agents are below a total reward of 0
|
||||
elif positive is False:
|
||||
assert agent_reward <= 0 # Asserts that no agents are above a total reward of 0
|
||||
else:
|
||||
print("Invalid 'positive' argument.")
|
||||
|
||||
|
||||
def test_green_agent_positive_reward(uc7_environment):
|
||||
"""Confirms that the UC7 Green Agents receive a positive reward (Default Behaviour)."""
|
||||
env: PrimaiteGymEnv = uc7_environment
|
||||
|
||||
# Performing no changes to the environment. Default Behaviour
|
||||
|
||||
# Stepping 60 times in the environment
|
||||
for _ in range(60):
|
||||
env.step(0)
|
||||
|
||||
for agent in env.game.agents:
|
||||
assert_agent_reward(env=env, agent_name=env.game.agents[agent].config.ref, positive=True)
|
||||
|
||||
|
||||
def test_green_agent_negative_reward(uc7_environment):
|
||||
"""Confirms that the UC7 Green Agents receive a negative reward. (Disabled web-server and database-service)"""
|
||||
|
||||
env: PrimaiteGymEnv = uc7_environment
|
||||
|
||||
# Purposefully disabling the following services:
|
||||
|
||||
# 1. Disabling the web-server
|
||||
st_dmz_pub_srv_web: Server = env.game.simulation.network.get_node_by_hostname("ST-DMZ-PUB-SRV-WEB")
|
||||
st_web_server = st_dmz_pub_srv_web.software_manager.software["web-server"]
|
||||
st_web_server.operating_state = ServiceOperatingState.DISABLED
|
||||
assert st_web_server.operating_state == ServiceOperatingState.DISABLED
|
||||
|
||||
# 2. Disabling the DatabaseServer
|
||||
st_data_database_server: Server = env.game.simulation.network.get_node_by_hostname("ST-DATA-PRV-SRV-DB")
|
||||
database_service: DatabaseService = st_data_database_server.software_manager.software["database-service"]
|
||||
database_service.operating_state = ServiceOperatingState.DISABLED
|
||||
assert database_service.operating_state == ServiceOperatingState.DISABLED
|
||||
|
||||
# Stepping 100 times in the environment
|
||||
for _ in range(100):
|
||||
env.step(0)
|
||||
|
||||
for agent in env.game.agents:
|
||||
assert_agent_reward(env=env, agent_name=env.game.agents[agent].config.ref, positive=False)
|
||||
|
||||
|
||||
def test_tap001_default_behaviour(uc7_environment):
|
||||
"""Confirms that the TAP001 expected simulation impacts works as expected in the UC7 environment."""
|
||||
env: PrimaiteGymEnv = uc7_environment
|
||||
env.reset()
|
||||
network = env.game.simulation.network
|
||||
|
||||
# Running for 128 episodes
|
||||
for _ in range(128):
|
||||
env.step(0)
|
||||
|
||||
some_tech_proj_a_pc_1: Computer = network.get_node_by_hostname("ST-PROJ-A-PRV-PC-1")
|
||||
|
||||
# Asserting that the `malware_dropper.ps1` was created.
|
||||
|
||||
malware_dropper_file: File = some_tech_proj_a_pc_1.file_system.get_file("downloads", "malware_dropper.ps1")
|
||||
assert malware_dropper_file.health_status == FileSystemItemHealthStatus.GOOD
|
||||
|
||||
# Asserting that the `RansomwareScript` launched successfully.
|
||||
|
||||
ransomware_script: RansomwareScript = some_tech_proj_a_pc_1.software_manager.software["ransomware-script"]
|
||||
assert ransomware_script.health_state_actual == SoftwareHealthState.GOOD
|
||||
assert ransomware_script.operating_state == ApplicationOperatingState.RUNNING
|
||||
|
||||
# Asserting that the `C2Beacon` connected to the `C2Server`.
|
||||
|
||||
c2_beacon: C2Beacon = some_tech_proj_a_pc_1.software_manager.software["c2-beacon"]
|
||||
assert c2_beacon.health_state_actual == SoftwareHealthState.GOOD
|
||||
assert c2_beacon.operating_state == ApplicationOperatingState.RUNNING
|
||||
assert c2_beacon.c2_connection_active == True
|
||||
|
||||
# Asserting that the target database was successfully corrupted.
|
||||
some_tech_data_server_database: Server = network.get_node_by_hostname("ST-DATA-PRV-SRV-DB")
|
||||
database_file: File = some_tech_data_server_database.file_system.get_file(
|
||||
folder_name="database", file_name="database.db"
|
||||
)
|
||||
assert database_file.health_status == FileSystemItemHealthStatus.CORRUPT
|
||||
|
||||
|
||||
def test_tap003_default_behaviour(uc7_environment):
|
||||
"""Confirms that the TAP003 expected simulation impacts works as expected in the UC7 environment."""
|
||||
from primaite.simulator.network.hardware.nodes.network.router import ACLAction, Router
|
||||
from primaite.simulator.network.transmission.network_layer import IPPacket, IPProtocol
|
||||
from primaite.utils.validation.port import PORT_LOOKUP
|
||||
|
||||
def uc7_environment_tap003() -> PrimaiteGymEnv:
|
||||
with open(_EXAMPLE_CFG / "uc7_config_tap003.yaml", mode="r") as uc7_config:
|
||||
cfg = yaml.safe_load(uc7_config)
|
||||
cfg["agents"][32]["agent_settings"]["starting_nodes"] = ["ST-PROJ-A-PRV-PC-1"]
|
||||
cfg["agents"][32]["agent_settings"]["default_starting_node"] = "ST-PROJ-A-PRV-PC-1"
|
||||
env = PrimaiteGymEnv(env_config=cfg)
|
||||
return env
|
||||
|
||||
env: PrimaiteGymEnv = uc7_environment_tap003()
|
||||
env.reset()
|
||||
# Running for 128 episodes
|
||||
for _ in range(128):
|
||||
env.step(0)
|
||||
network = env.game.simulation.network
|
||||
|
||||
# Asserting that a malicious ACL has been added to ST-INTRA-PRV-RT-DR-1
|
||||
st_intra_prv_rt_dr_1: Router = network.get_node_by_hostname(hostname="ST-INTRA-PRV-RT-DR-1")
|
||||
assert st_intra_prv_rt_dr_1.acl.acl[1].action == ACLAction.DENY
|
||||
assert st_intra_prv_rt_dr_1.acl.acl[1].protocol == "tcp"
|
||||
assert st_intra_prv_rt_dr_1.acl.acl[1].src_port == PORT_LOOKUP.get("POSTGRES_SERVER")
|
||||
assert st_intra_prv_rt_dr_1.acl.acl[1].dst_port == PORT_LOOKUP.get("POSTGRES_SERVER")
|
||||
|
||||
# Asserting that a malicious ACL has been added to ST-INTRA-PRV-RT-CR
|
||||
st_intra_prv_rt_cr: Router = network.get_node_by_hostname(hostname="ST-INTRA-PRV-RT-CR")
|
||||
assert st_intra_prv_rt_cr.acl.acl[1].action == ACLAction.DENY
|
||||
assert st_intra_prv_rt_cr.acl.acl[1].protocol == "tcp"
|
||||
assert st_intra_prv_rt_cr.acl.acl[1].src_port == PORT_LOOKUP.get("HTTP")
|
||||
assert st_intra_prv_rt_cr.acl.acl[1].dst_port == PORT_LOOKUP.get("HTTP")
|
||||
|
||||
# Asserting that a malicious ACL has been added to REM-PUB-RT-DR
|
||||
rem_pub_rt_dr: Router = network.get_node_by_hostname(hostname="REM-PUB-RT-DR")
|
||||
assert rem_pub_rt_dr.acl.acl[1].action == ACLAction.DENY
|
||||
assert rem_pub_rt_dr.acl.acl[1].protocol == "tcp"
|
||||
assert rem_pub_rt_dr.acl.acl[1].src_port == PORT_LOOKUP.get("DNS")
|
||||
assert rem_pub_rt_dr.acl.acl[1].dst_port == PORT_LOOKUP.get("DNS")
|
||||
237
tests/e2e_integration_tests/test_uc7_route_connectivity.py
Normal file
237
tests/e2e_integration_tests/test_uc7_route_connectivity.py
Normal file
@@ -0,0 +1,237 @@
|
||||
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
from primaite.config.load import _EXAMPLE_CFG
|
||||
from primaite.game.game import PrimaiteGame
|
||||
from primaite.session.environment import PrimaiteGymEnv
|
||||
from primaite.simulator.network.container import Network
|
||||
from primaite.simulator.network.hardware.nodes.host.computer import Computer
|
||||
from primaite.simulator.network.hardware.nodes.host.server import Server
|
||||
from primaite.simulator.network.hardware.nodes.network.firewall import Firewall
|
||||
from primaite.simulator.network.hardware.nodes.network.router import Router
|
||||
from primaite.simulator.network.hardware.nodes.network.switch import Switch
|
||||
|
||||
CONFIG_FILE = _EXAMPLE_CFG / "uc7_config.yaml"
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def uc7_network() -> Network:
|
||||
with open(file=CONFIG_FILE, mode="r") as f:
|
||||
cfg = yaml.safe_load(stream=f)
|
||||
|
||||
game = PrimaiteGame.from_config(cfg=cfg)
|
||||
return game.simulation.network
|
||||
|
||||
|
||||
def test_ping_home_office(uc7_network):
|
||||
"""Asserts that all home_pub_* can ping each-other and the public dns (isp_pub_srv_dns)"""
|
||||
network = uc7_network
|
||||
home_pub_pc_1: Computer = network.get_node_by_hostname("HOME-PUB-PC-1")
|
||||
home_pub_pc_2: Computer = network.get_node_by_hostname("HOME-PUB-PC-2")
|
||||
home_pub_pc_srv: Server = network.get_node_by_hostname("HOME-PUB-SRV")
|
||||
home_pub_rt_dr: Router = network.get_node_by_hostname("HOME-PUB-RT-DR")
|
||||
isp_pub_srv_dns: Server = network.get_node_by_hostname("ISP-PUB-SRV-DNS")
|
||||
|
||||
assert home_pub_pc_1.ping(isp_pub_srv_dns.network_interface[1].ip_address)
|
||||
|
||||
def ping_all_home_office(host):
|
||||
assert host.ping(home_pub_pc_1.network_interface[1].ip_address)
|
||||
assert host.ping(home_pub_pc_2.network_interface[1].ip_address)
|
||||
assert host.ping(home_pub_pc_srv.network_interface[1].ip_address)
|
||||
assert host.ping(home_pub_rt_dr.network_interface[1].ip_address)
|
||||
assert host.ping(isp_pub_srv_dns.network_interface[1].ip_address)
|
||||
|
||||
ping_all_home_office(home_pub_pc_1)
|
||||
ping_all_home_office(home_pub_pc_2)
|
||||
ping_all_home_office(home_pub_pc_srv)
|
||||
ping_all_home_office(isp_pub_srv_dns)
|
||||
|
||||
|
||||
def test_ping_remote_site(uc7_network):
|
||||
"""Asserts that all remote_pub_* hosts can ping each-other and the public dns server (isp_pub_srv_dns)"""
|
||||
network = uc7_network
|
||||
rem_pub_fw: Firewall = network.get_node_by_hostname(hostname="REM-PUB-FW")
|
||||
rem_pub_rt_dr: Router = network.get_node_by_hostname(hostname="REM-PUB-RT-DR")
|
||||
rem_pub_pc_1: Computer = network.get_node_by_hostname(hostname="REM-PUB-PC-1")
|
||||
rem_pub_pc_2: Computer = network.get_node_by_hostname(hostname="REM-PUB-PC-2")
|
||||
rem_pub_srv: Computer = network.get_node_by_hostname(hostname="REM-PUB-SRV")
|
||||
|
||||
def ping_all_remote_site(host):
|
||||
assert host.ping(rem_pub_fw.network_interface[1].ip_address)
|
||||
assert host.ping(rem_pub_rt_dr.network_interface[1].ip_address)
|
||||
assert host.ping(rem_pub_pc_1.network_interface[1].ip_address)
|
||||
assert host.ping(rem_pub_pc_2.network_interface[1].ip_address)
|
||||
assert host.ping(rem_pub_srv.network_interface[1].ip_address)
|
||||
|
||||
ping_all_remote_site(host=rem_pub_fw)
|
||||
ping_all_remote_site(host=rem_pub_rt_dr)
|
||||
ping_all_remote_site(host=rem_pub_pc_1)
|
||||
ping_all_remote_site(host=rem_pub_pc_2)
|
||||
ping_all_remote_site(host=rem_pub_srv)
|
||||
|
||||
|
||||
def test_ping_some_tech_dmz(uc7_network):
|
||||
"""Asserts that the st_dmz_pub_srv_web and the st_public_firewall can ping each other and remote site and home office."""
|
||||
network = uc7_network
|
||||
st_pub_fw: Firewall = network.get_node_by_hostname(hostname="ST-PUB-FW")
|
||||
st_dmz_pub_srv_web: Server = network.get_node_by_hostname(hostname="ST-DMZ-PUB-SRV-WEB")
|
||||
isp_pub_srv_dns: Server = network.get_node_by_hostname("ISP-PUB-SRV-DNS")
|
||||
home_pub_pc_1: Computer = network.get_node_by_hostname("HOME-PUB-PC-1")
|
||||
|
||||
def ping_all_some_tech_dmz(host):
|
||||
assert host.ping(st_dmz_pub_srv_web.network_interface[1].ip_address)
|
||||
assert host.ping(isp_pub_srv_dns.network_interface[1].ip_address)
|
||||
|
||||
ping_all_some_tech_dmz(host=st_pub_fw)
|
||||
ping_all_some_tech_dmz(host=isp_pub_srv_dns)
|
||||
ping_all_some_tech_dmz(host=home_pub_pc_1)
|
||||
|
||||
|
||||
def test_ping_some_tech_head_office(uc7_network):
|
||||
"""Asserts that all the some_tech_* PCs can ping each other and the public dns"""
|
||||
network = uc7_network
|
||||
st_home_office_private_pc_1: Computer = network.get_node_by_hostname("ST-HO-PRV-PC-1")
|
||||
st_home_office_private_pc_2: Computer = network.get_node_by_hostname("ST-HO-PRV-PC-2")
|
||||
st_home_office_private_pc_3: Computer = network.get_node_by_hostname("ST-HO-PRV-PC-3")
|
||||
isp_pub_srv_dns: Server = network.get_node_by_hostname("ISP-PUB-SRV-DNS")
|
||||
|
||||
def ping_all_some_tech_head_office(host):
|
||||
assert host.ping(st_home_office_private_pc_1.network_interface[1].ip_address)
|
||||
assert host.ping(st_home_office_private_pc_2.network_interface[1].ip_address)
|
||||
assert host.ping(st_home_office_private_pc_3.network_interface[1].ip_address)
|
||||
assert host.ping(isp_pub_srv_dns.network_interface[1].ip_address)
|
||||
|
||||
ping_all_some_tech_head_office(host=st_home_office_private_pc_1)
|
||||
ping_all_some_tech_head_office(host=st_home_office_private_pc_2)
|
||||
ping_all_some_tech_head_office(host=st_home_office_private_pc_3)
|
||||
|
||||
|
||||
def test_ping_some_tech_hr(uc7_network):
|
||||
"""Assert that all some_tech_hr_* PCs can ping each other and the public dns"""
|
||||
network = uc7_network
|
||||
some_tech_hr_pc_1: Computer = network.get_node_by_hostname("ST-HR-PRV-PC-1")
|
||||
some_tech_hr_pc_2: Computer = network.get_node_by_hostname("ST-HR-PRV-PC-2")
|
||||
some_tech_hr_pc_3: Computer = network.get_node_by_hostname("ST-HR-PRV-PC-3")
|
||||
isp_pub_srv_dns: Server = network.get_node_by_hostname("ISP-PUB-SRV-DNS")
|
||||
|
||||
def ping_all_some_tech_hr(host):
|
||||
assert host.ping(some_tech_hr_pc_1.network_interface[1].ip_address)
|
||||
assert host.ping(some_tech_hr_pc_2.network_interface[1].ip_address)
|
||||
assert host.ping(some_tech_hr_pc_3.network_interface[1].ip_address)
|
||||
assert host.ping(isp_pub_srv_dns.network_interface[1].ip_address)
|
||||
|
||||
ping_all_some_tech_hr(some_tech_hr_pc_1)
|
||||
ping_all_some_tech_hr(some_tech_hr_pc_2)
|
||||
ping_all_some_tech_hr(some_tech_hr_pc_3)
|
||||
|
||||
|
||||
def test_some_tech_data_hr(uc7_network):
|
||||
"""Assert that all some_tech_data_* servers can ping each other and the public dns."""
|
||||
network = uc7_network
|
||||
some_tech_data_server_storage: Server = network.get_node_by_hostname("ST-DATA-PRV-SRV-STORAGE")
|
||||
some_tech_data_server_database: Server = network.get_node_by_hostname("ST-DATA-PRV-SRV-DB")
|
||||
isp_pub_srv_dns: Server = network.get_node_by_hostname("ISP-PUB-SRV-DNS")
|
||||
|
||||
def ping_all_some_tech_hr(host):
|
||||
assert host.ping(some_tech_data_server_storage.network_interface[1].ip_address)
|
||||
assert host.ping(some_tech_data_server_database.network_interface[1].ip_address)
|
||||
assert host.ping(isp_pub_srv_dns.network_interface[1].ip_address)
|
||||
|
||||
ping_all_some_tech_hr(some_tech_data_server_storage)
|
||||
ping_all_some_tech_hr(some_tech_data_server_database)
|
||||
|
||||
|
||||
def test_some_tech_project_a(uc7_network):
|
||||
"""Asserts that all some_tech project A's PCs can ping each other and the public dns."""
|
||||
network = uc7_network
|
||||
some_tech_proj_a_pc_1: Computer = network.get_node_by_hostname("ST-PROJ-A-PRV-PC-1")
|
||||
some_tech_proj_a_pc_2: Computer = network.get_node_by_hostname("ST-PROJ-A-PRV-PC-2")
|
||||
some_tech_proj_a_pc_3: Computer = network.get_node_by_hostname("ST-PROJ-A-PRV-PC-3")
|
||||
isp_pub_srv_dns: Server = network.get_node_by_hostname("ISP-PUB-SRV-DNS")
|
||||
|
||||
def ping_all_some_tech_proj_a(host):
|
||||
assert host.ping(some_tech_proj_a_pc_1.network_interface[1].ip_address)
|
||||
assert host.ping(some_tech_proj_a_pc_2.network_interface[1].ip_address)
|
||||
assert host.ping(some_tech_proj_a_pc_3.network_interface[1].ip_address)
|
||||
assert host.ping(isp_pub_srv_dns.network_interface[1].ip_address)
|
||||
|
||||
ping_all_some_tech_proj_a(some_tech_proj_a_pc_1)
|
||||
ping_all_some_tech_proj_a(some_tech_proj_a_pc_2)
|
||||
ping_all_some_tech_proj_a(some_tech_proj_a_pc_3)
|
||||
|
||||
|
||||
def test_some_tech_project_b(uc7_network):
|
||||
"""Asserts that all some_tech_project_b PC's can ping each other and the public dps."""
|
||||
network = uc7_network
|
||||
some_tech_proj_b_pc_1: Computer = network.get_node_by_hostname("ST-PROJ-B-PRV-PC-1")
|
||||
some_tech_proj_b_pc_2: Computer = network.get_node_by_hostname("ST-PROJ-B-PRV-PC-2")
|
||||
some_tech_proj_b_pc_3: Computer = network.get_node_by_hostname("ST-PROJ-B-PRV-PC-3")
|
||||
isp_pub_srv_dns: Server = network.get_node_by_hostname("ISP-PUB-SRV-DNS")
|
||||
|
||||
def ping_all_some_tech_proj_b(host):
|
||||
assert host.ping(some_tech_proj_b_pc_1.network_interface[1].ip_address)
|
||||
assert host.ping(some_tech_proj_b_pc_2.network_interface[1].ip_address)
|
||||
assert host.ping(some_tech_proj_b_pc_3.network_interface[1].ip_address)
|
||||
assert host.ping(isp_pub_srv_dns.network_interface[1].ip_address)
|
||||
|
||||
ping_all_some_tech_proj_b(some_tech_proj_b_pc_1)
|
||||
ping_all_some_tech_proj_b(some_tech_proj_b_pc_2)
|
||||
ping_all_some_tech_proj_b(some_tech_proj_b_pc_3)
|
||||
|
||||
|
||||
def test_some_tech_project_a(uc7_network):
|
||||
"""Asserts that all some_tech_project_c PC's can ping each other and the public dps."""
|
||||
network = uc7_network
|
||||
some_tech_proj_c_pc_1: Computer = network.get_node_by_hostname("ST-PROJ-C-PRV-PC-1")
|
||||
some_tech_proj_c_pc_2: Computer = network.get_node_by_hostname("ST-PROJ-C-PRV-PC-2")
|
||||
some_tech_proj_c_pc_3: Computer = network.get_node_by_hostname("ST-PROJ-C-PRV-PC-3")
|
||||
isp_pub_srv_dns: Server = network.get_node_by_hostname("ISP-PUB-SRV-DNS")
|
||||
|
||||
def ping_all_some_tech_proj_c(host):
|
||||
assert host.ping(some_tech_proj_c_pc_1.network_interface[1].ip_address)
|
||||
assert host.ping(some_tech_proj_c_pc_2.network_interface[1].ip_address)
|
||||
assert host.ping(some_tech_proj_c_pc_3.network_interface[1].ip_address)
|
||||
assert host.ping(isp_pub_srv_dns.network_interface[1].ip_address)
|
||||
|
||||
ping_all_some_tech_proj_c(some_tech_proj_c_pc_1)
|
||||
ping_all_some_tech_proj_c(some_tech_proj_c_pc_2)
|
||||
ping_all_some_tech_proj_c(some_tech_proj_c_pc_3)
|
||||
|
||||
|
||||
def test_ping_all_networks(uc7_network):
|
||||
"""Asserts that one machine from each network is able to ping all others."""
|
||||
network = uc7_network
|
||||
home_office_pc_1: Computer = network.get_node_by_hostname("HOME-PUB-PC-1")
|
||||
isp_pub_srv_dns: Server = network.get_node_by_hostname("ISP-PUB-SRV-DNS")
|
||||
remote_office_pc_1: Computer = network.get_node_by_hostname("REM-PUB-PC-1")
|
||||
st_head_office_pc_1: Computer = network.get_node_by_hostname("ST-HO-PRV-PC-1")
|
||||
st_human_resources_pc_1: Computer = network.get_node_by_hostname("ST-HR-PRV-PC-1")
|
||||
st_data_storage_server: Server = network.get_node_by_hostname("ST-DATA-PRV-SRV-STORAGE")
|
||||
st_data_database_server: Server = network.get_node_by_hostname("ST-DATA-PRV-SRV-DB")
|
||||
st_proj_a_pc_1: Computer = network.get_node_by_hostname("ST-PROJ-A-PRV-PC-1")
|
||||
st_proj_b_pc_1: Computer = network.get_node_by_hostname("ST-PROJ-B-PRV-PC-1")
|
||||
st_proj_c_pc_1: Computer = network.get_node_by_hostname("ST-PROJ-C-PRV-PC-1")
|
||||
|
||||
def ping_network_wide(host):
|
||||
assert host.ping(home_office_pc_1.network_interface[1].ip_address)
|
||||
assert host.ping(isp_pub_srv_dns.network_interface[1].ip_address)
|
||||
assert host.ping(remote_office_pc_1.network_interface[1].ip_address)
|
||||
assert host.ping(st_head_office_pc_1.network_interface[1].ip_address)
|
||||
assert host.ping(st_human_resources_pc_1.network_interface[1].ip_address)
|
||||
assert host.ping(st_data_storage_server.network_interface[1].ip_address)
|
||||
assert host.ping(st_data_database_server.network_interface[1].ip_address)
|
||||
assert host.ping(st_proj_a_pc_1.network_interface[1].ip_address)
|
||||
assert host.ping(st_proj_b_pc_1.network_interface[1].ip_address)
|
||||
assert host.ping(st_proj_c_pc_1.network_interface[1].ip_address)
|
||||
|
||||
ping_network_wide(host=home_office_pc_1)
|
||||
ping_network_wide(host=isp_pub_srv_dns)
|
||||
ping_network_wide(host=remote_office_pc_1)
|
||||
ping_network_wide(host=st_head_office_pc_1)
|
||||
ping_network_wide(host=st_human_resources_pc_1)
|
||||
ping_network_wide(host=st_data_storage_server)
|
||||
ping_network_wide(host=st_data_database_server)
|
||||
ping_network_wide(host=st_proj_a_pc_1)
|
||||
ping_network_wide(host=st_proj_b_pc_1)
|
||||
ping_network_wide(host=st_proj_c_pc_1)
|
||||
@@ -0,0 +1,338 @@
|
||||
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
from primaite.config.load import _EXAMPLE_CFG
|
||||
from primaite.game.game import PrimaiteGame
|
||||
from primaite.session.environment import PrimaiteGymEnv
|
||||
from primaite.simulator.network.container import Network
|
||||
from primaite.simulator.network.hardware.nodes.host.computer import Computer
|
||||
from primaite.simulator.network.hardware.nodes.host.server import Server
|
||||
from primaite.simulator.system.applications.application import ApplicationOperatingState
|
||||
from primaite.simulator.system.applications.database_client import DatabaseClient
|
||||
from primaite.simulator.system.applications.web_browser import WebBrowser
|
||||
from primaite.simulator.system.services.database.database_service import DatabaseService
|
||||
from primaite.simulator.system.services.dns.dns_client import DNSClient
|
||||
from primaite.simulator.system.services.dns.dns_server import DNSServer
|
||||
from primaite.simulator.system.services.ftp.ftp_client import FTPClient
|
||||
from primaite.simulator.system.services.ftp.ftp_server import FTPServer
|
||||
from primaite.simulator.system.services.ntp.ntp_client import NTPClient
|
||||
from primaite.simulator.system.services.ntp.ntp_server import NTPServer
|
||||
from primaite.simulator.system.services.service import ServiceOperatingState
|
||||
from primaite.simulator.system.software import SoftwareHealthState
|
||||
|
||||
CONFIG_FILE = _EXAMPLE_CFG / "uc7_config.yaml"
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def uc7_network() -> Network:
|
||||
with open(file=CONFIG_FILE, mode="r") as f:
|
||||
cfg = yaml.safe_load(stream=f)
|
||||
|
||||
game = PrimaiteGame.from_config(cfg=cfg)
|
||||
return game.simulation.network
|
||||
|
||||
|
||||
def assert_ntp_client(host):
|
||||
"""Confirms that the ntp_client service is present and functioning."""
|
||||
ntp_client: NTPClient = host.software_manager.software["ntp-client"]
|
||||
assert ntp_client is not None
|
||||
assert ntp_client.operating_state == ServiceOperatingState.RUNNING
|
||||
assert ntp_client.health_state_actual == SoftwareHealthState.GOOD
|
||||
|
||||
|
||||
def assert_dns_client(host):
|
||||
"""Confirms that the dns_client service is present and functioning."""
|
||||
dns_client: DNSClient = host.software_manager.software["dns-client"]
|
||||
assert dns_client is not None
|
||||
assert dns_client.operating_state == ServiceOperatingState.RUNNING
|
||||
assert dns_client.health_state_actual == SoftwareHealthState.GOOD
|
||||
|
||||
|
||||
def assert_web_browser(host: Computer):
|
||||
"""Asserts that the web_browser application is present and functioning."""
|
||||
web_browser: WebBrowser = host.software_manager.software["web-browser"]
|
||||
assert web_browser is not None
|
||||
assert web_browser.operating_state == ApplicationOperatingState.RUNNING
|
||||
assert web_browser.health_state_actual == SoftwareHealthState.GOOD
|
||||
|
||||
|
||||
def assert_database_client(host: Computer):
|
||||
"""Asserts that the database_client application is present and functioning."""
|
||||
database_client = host.software_manager.software["database-client"]
|
||||
assert database_client is not None
|
||||
assert database_client.operating_state == ApplicationOperatingState.RUNNING
|
||||
assert database_client.health_state_actual == SoftwareHealthState.GOOD
|
||||
|
||||
|
||||
def test_home_office_software(uc7_network):
|
||||
"""Asserts that each host in the home_office network contains the expected software."""
|
||||
network: Network = uc7_network
|
||||
home_pub_pc_1: Computer = network.get_node_by_hostname("HOME-PUB-PC-1")
|
||||
home_pub_pc_2: Computer = network.get_node_by_hostname("HOME-PUB-PC-1")
|
||||
home_pub_srv: Server = network.get_node_by_hostname("HOME-PUB-SRV")
|
||||
|
||||
# Home Office PC 1
|
||||
assert_web_browser(home_pub_pc_1)
|
||||
assert_database_client(home_pub_pc_1)
|
||||
assert_dns_client(home_pub_pc_1)
|
||||
assert_ntp_client(home_pub_pc_1)
|
||||
|
||||
# Home Office PC 2
|
||||
assert_web_browser(home_pub_pc_2)
|
||||
assert_database_client(home_pub_pc_2)
|
||||
assert_dns_client(home_pub_pc_2)
|
||||
assert_ntp_client(home_pub_pc_2)
|
||||
|
||||
# Home Office Server
|
||||
assert_dns_client(home_pub_srv)
|
||||
assert_ntp_client(home_pub_srv)
|
||||
|
||||
|
||||
def test_internet_dns_server(uc7_network):
|
||||
"""Asserts that `ISP-PUB-SRV-DNS` host's DNSServer application is operating and functioning as expected."""
|
||||
network: Network = uc7_network
|
||||
isp_pub_srv_dns: Server = network.get_node_by_hostname("ISP-PUB-SRV-DNS")
|
||||
|
||||
# Confirming that the DNSServer is up and running:
|
||||
|
||||
dns_server: DNSServer = isp_pub_srv_dns.software_manager.software["dns-server"]
|
||||
assert dns_server is not None
|
||||
assert dns_server.operating_state == ServiceOperatingState.RUNNING
|
||||
assert dns_server.health_state_actual == SoftwareHealthState.GOOD
|
||||
|
||||
# Confirming that the DNSServer is performing as expected by performing a request from a client
|
||||
|
||||
home_pub_pc_1: Computer = network.get_node_by_hostname("HOME-PUB-PC-1")
|
||||
dns_client: DNSClient = home_pub_pc_1.software_manager.software["dns-client"]
|
||||
|
||||
assert dns_client.check_domain_exists(target_domain="some_tech.com")
|
||||
assert dns_client.dns_cache.get("some_tech.com", None) is not None
|
||||
assert len(dns_client.dns_cache) == 1
|
||||
|
||||
|
||||
def test_remote_office_software(uc7_network):
|
||||
"""Asserts that each host on the remote_office network has the expected services & applications which are operating as expected."""
|
||||
network = uc7_network
|
||||
rem_pub_pc_1: Computer = network.get_node_by_hostname(hostname="REM-PUB-PC-1")
|
||||
rem_pub_pc_2: Computer = network.get_node_by_hostname(hostname="REM-PUB-PC-2")
|
||||
rem_pub_srv: Server = network.get_node_by_hostname(hostname="REM-PUB-SRV")
|
||||
|
||||
# Remote Site PC 1
|
||||
assert_web_browser(rem_pub_pc_1)
|
||||
assert_database_client(rem_pub_pc_1)
|
||||
assert_dns_client(rem_pub_pc_1)
|
||||
assert_ntp_client(rem_pub_pc_1)
|
||||
|
||||
# Remote Site PC 2
|
||||
assert_web_browser(rem_pub_pc_2)
|
||||
assert_database_client(rem_pub_pc_2)
|
||||
assert_dns_client(rem_pub_pc_2)
|
||||
assert_ntp_client(rem_pub_pc_2)
|
||||
|
||||
# Remote Site Server
|
||||
assert_dns_client(rem_pub_srv)
|
||||
assert_ntp_client(rem_pub_srv)
|
||||
|
||||
|
||||
def test_dmz_web_server(uc7_network):
|
||||
"""Asserts that the DMZ WebServer functions as expected"""
|
||||
network: Network = uc7_network
|
||||
st_dmz_pub_srv_web: Server = network.get_node_by_hostname("ST-DMZ-PUB-SRV-WEB")
|
||||
|
||||
# Asserting the ST Web Server is working as expected
|
||||
st_web_server = st_dmz_pub_srv_web.software_manager.software["web-server"]
|
||||
assert st_web_server is not None
|
||||
assert st_web_server.operating_state == ServiceOperatingState.RUNNING
|
||||
assert st_web_server.health_state_actual == SoftwareHealthState.GOOD
|
||||
|
||||
# Asserting that WebBrowser can actually connect to the WebServer
|
||||
|
||||
# SOME TECH Human Resources --> DMZ Web Server
|
||||
st_hr_pc_1: Computer = network.get_node_by_hostname("ST-HR-PRV-PC-1")
|
||||
st_hr_pc_1_web_browser: WebBrowser = st_hr_pc_1.software_manager.software["web-browser"]
|
||||
assert st_hr_pc_1_web_browser.get_webpage("http://some_tech.com")
|
||||
|
||||
# Remote Site --> DMZ Web Server
|
||||
rem_pub_pc_1: Computer = network.get_node_by_hostname("REM-PUB-PC-1")
|
||||
rem_pub_pc_1_web_browser: WebBrowser = rem_pub_pc_1.software_manager.software["web-browser"]
|
||||
assert rem_pub_pc_1_web_browser.get_webpage("http://some_tech.com")
|
||||
|
||||
# Home Office --> DMZ Web Server
|
||||
home_pub_pc_1: Computer = network.get_node_by_hostname("HOME-PUB-PC-1")
|
||||
home_pub_pc_1_web_browser: WebBrowser = home_pub_pc_1.software_manager.software["web-browser"]
|
||||
assert home_pub_pc_1_web_browser.get_webpage("http://some_tech.com")
|
||||
|
||||
|
||||
def test_tech_head_office_software(uc7_network):
|
||||
"""Asserts that each host on the some_tech_head_office network has the expected services & applications which are operating as expected."""
|
||||
network: Network = uc7_network
|
||||
|
||||
st_head_office_private_pc_1: Computer = network.get_node_by_hostname("ST-HO-PRV-PC-1")
|
||||
st_head_office_private_pc_2: Computer = network.get_node_by_hostname("ST-HO-PRV-PC-2")
|
||||
st_head_office_private_pc_3: Computer = network.get_node_by_hostname("ST-HO-PRV-PC-3")
|
||||
|
||||
# ST Head Office One
|
||||
|
||||
assert_web_browser(st_head_office_private_pc_1)
|
||||
assert_database_client(st_head_office_private_pc_1)
|
||||
assert_dns_client(st_head_office_private_pc_1)
|
||||
assert_ntp_client(st_head_office_private_pc_1)
|
||||
|
||||
# ST Head Office Two
|
||||
|
||||
assert_web_browser(st_head_office_private_pc_2)
|
||||
assert_database_client(st_head_office_private_pc_2)
|
||||
assert_dns_client(st_head_office_private_pc_2)
|
||||
assert_ntp_client(st_head_office_private_pc_2)
|
||||
|
||||
# ST Head Office Three
|
||||
|
||||
assert_web_browser(st_head_office_private_pc_3)
|
||||
assert_database_client(st_head_office_private_pc_3)
|
||||
assert_dns_client(st_head_office_private_pc_3)
|
||||
assert_ntp_client(st_head_office_private_pc_3)
|
||||
|
||||
|
||||
def test_tech_human_resources_office_software(uc7_network):
|
||||
"""Asserts that each host on the some_tech human_resources network has the expected services & applications which are operating as expected."""
|
||||
network: Network = uc7_network
|
||||
|
||||
st_hr_pc_1: Computer = network.get_node_by_hostname("ST-HR-PRV-PC-1")
|
||||
st_hr_pc_2: Computer = network.get_node_by_hostname("ST-HR-PRV-PC-2")
|
||||
st_hr_pc_3: Computer = network.get_node_by_hostname("ST-HR-PRV-PC-3")
|
||||
|
||||
# ST Human Resource PC 1
|
||||
|
||||
assert_web_browser(st_hr_pc_1)
|
||||
assert_database_client(st_hr_pc_1)
|
||||
assert_dns_client(st_hr_pc_1)
|
||||
assert_ntp_client(st_hr_pc_1)
|
||||
|
||||
# ST Human Resource PC 2
|
||||
|
||||
assert_web_browser(st_hr_pc_2)
|
||||
assert_database_client(st_hr_pc_2)
|
||||
assert_dns_client(st_hr_pc_2)
|
||||
assert_ntp_client(st_hr_pc_2)
|
||||
|
||||
# ST Human Resource PC 3
|
||||
|
||||
assert_web_browser(st_hr_pc_3)
|
||||
assert_database_client(st_hr_pc_3)
|
||||
assert_dns_client(st_hr_pc_3)
|
||||
assert_ntp_client(st_hr_pc_3)
|
||||
|
||||
|
||||
def test_tech_data_software(uc7_network):
|
||||
"""Asserts the database and database storage servers on the some_tech data network are operating as expected."""
|
||||
network: Network = uc7_network
|
||||
st_data_database_server: Server = network.get_node_by_hostname("ST-DATA-PRV-SRV-DB")
|
||||
st_data_database_storage: Server = network.get_node_by_hostname("ST-DATA-PRV-SRV-STORAGE")
|
||||
st_proj_a_pc_1: Computer = network.get_node_by_hostname("ST-PROJ-A-PRV-PC-1")
|
||||
|
||||
# Asserting that the database_service is working as expected
|
||||
database_service: DatabaseService = st_data_database_server.software_manager.software["database-service"]
|
||||
|
||||
assert database_service is not None
|
||||
assert database_service.operating_state == ServiceOperatingState.RUNNING
|
||||
assert database_service.health_state_actual == SoftwareHealthState.GOOD
|
||||
|
||||
# Asserting that the database_client can connect to the database
|
||||
database_client: DatabaseClient = st_proj_a_pc_1.software_manager.software["database-client"]
|
||||
|
||||
assert database_client.server_ip_address is not None
|
||||
assert database_client.server_ip_address == st_data_database_server.network_interface[1].ip_address
|
||||
assert database_client.connect()
|
||||
|
||||
# Asserting that the database storage works as expected.
|
||||
assert database_service.backup_server_ip == st_data_database_storage.network_interface[1].ip_address
|
||||
assert database_service.backup_database()
|
||||
|
||||
|
||||
def test_tech_proj_a_software(uc7_network):
|
||||
"""Asserts that each host on the some_tech project A network has the expected services & applications which are operating as expected."""
|
||||
network: Network = uc7_network
|
||||
st_proj_a_pc_1: Computer = network.get_node_by_hostname("ST-PROJ-A-PRV-PC-1")
|
||||
st_proj_a_pc_2: Computer = network.get_node_by_hostname("ST-PROJ-A-PRV-PC-2")
|
||||
st_proj_a_pc_3: Computer = network.get_node_by_hostname("ST-PROJ-A-PRV-PC-3")
|
||||
|
||||
# ST Project A - PC 1
|
||||
|
||||
assert_web_browser(st_proj_a_pc_1)
|
||||
assert_database_client(st_proj_a_pc_1)
|
||||
assert_dns_client(st_proj_a_pc_1)
|
||||
assert_ntp_client(st_proj_a_pc_1)
|
||||
|
||||
# ST Project A - PC 2
|
||||
|
||||
assert_web_browser(st_proj_a_pc_2)
|
||||
assert_database_client(st_proj_a_pc_2)
|
||||
assert_dns_client(st_proj_a_pc_2)
|
||||
assert_ntp_client(st_proj_a_pc_2)
|
||||
|
||||
# ST Project A - PC 3
|
||||
|
||||
assert_web_browser(st_proj_a_pc_3)
|
||||
assert_database_client(st_proj_a_pc_3)
|
||||
assert_dns_client(st_proj_a_pc_3)
|
||||
assert_ntp_client(st_proj_a_pc_3)
|
||||
|
||||
|
||||
def test_tech_proj_b_software(uc7_network):
|
||||
"""Asserts that each host on the some_tech project A network has the expected services & applications which are operating as expected."""
|
||||
network: Network = uc7_network
|
||||
st_proj_b_pc_1: Computer = network.get_node_by_hostname("ST-PROJ-B-PRV-PC-1")
|
||||
st_proj_b_pc_2: Computer = network.get_node_by_hostname("ST-PROJ-B-PRV-PC-2")
|
||||
st_proj_b_pc_3: Computer = network.get_node_by_hostname("ST-PROJ-B-PRV-PC-3")
|
||||
|
||||
# ST Project B - PC 1
|
||||
|
||||
assert_web_browser(st_proj_b_pc_1)
|
||||
assert_database_client(st_proj_b_pc_1)
|
||||
assert_dns_client(st_proj_b_pc_1)
|
||||
assert_ntp_client(st_proj_b_pc_1)
|
||||
|
||||
# ST Project B - PC2
|
||||
|
||||
assert_web_browser(st_proj_b_pc_2)
|
||||
assert_database_client(st_proj_b_pc_2)
|
||||
assert_dns_client(st_proj_b_pc_2)
|
||||
assert_ntp_client(st_proj_b_pc_2)
|
||||
|
||||
# ST Project B - PC3
|
||||
|
||||
assert_web_browser(st_proj_b_pc_3)
|
||||
assert_database_client(st_proj_b_pc_3)
|
||||
assert_dns_client(st_proj_b_pc_3)
|
||||
assert_ntp_client(st_proj_b_pc_3)
|
||||
|
||||
|
||||
def test_tech_proj_c_software(uc7_network):
|
||||
"""Asserts that each host on the some_tech project A network has the expected services & applications which are operating as expected."""
|
||||
network: Network = uc7_network
|
||||
st_proj_c_pc_1: Computer = network.get_node_by_hostname("ST-PROJ-C-PRV-PC-1")
|
||||
st_proj_c_pc_2: Computer = network.get_node_by_hostname("ST-PROJ-C-PRV-PC-2")
|
||||
st_proj_c_pc_3: Computer = network.get_node_by_hostname("ST-PROJ-C-PRV-PC-3")
|
||||
|
||||
# ST Project C - PC 1
|
||||
|
||||
assert_web_browser(st_proj_c_pc_1)
|
||||
assert_database_client(st_proj_c_pc_1)
|
||||
assert_dns_client(st_proj_c_pc_1)
|
||||
assert_ntp_client(st_proj_c_pc_1)
|
||||
|
||||
# ST Project C - PC2
|
||||
|
||||
assert_web_browser(st_proj_c_pc_2)
|
||||
assert_database_client(st_proj_c_pc_2)
|
||||
assert_dns_client(st_proj_c_pc_2)
|
||||
assert_ntp_client(st_proj_c_pc_2)
|
||||
|
||||
# ST Project C - PC3
|
||||
|
||||
assert_web_browser(st_proj_c_pc_3)
|
||||
assert_database_client(st_proj_c_pc_3)
|
||||
assert_dns_client(st_proj_c_pc_3)
|
||||
assert_ntp_client(st_proj_c_pc_3)
|
||||
@@ -0,0 +1 @@
|
||||
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
|
||||
@@ -0,0 +1,143 @@
|
||||
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
from primaite.config.load import _EXAMPLE_CFG
|
||||
from primaite.game.agent.scripted_agents.abstract_tap import (
|
||||
AbstractTAP,
|
||||
BaseKillChain,
|
||||
KillChainOptions,
|
||||
KillChainStageOptions,
|
||||
KillChainStageProgress,
|
||||
)
|
||||
from primaite.game.agent.scripted_agents.TAP001 import MobileMalwareKillChain
|
||||
from primaite.game.agent.scripted_agents.TAP003 import InsiderKillChain
|
||||
from primaite.session.environment import PrimaiteGymEnv
|
||||
|
||||
|
||||
def uc7_tap001_env() -> PrimaiteGymEnv:
|
||||
with open(_EXAMPLE_CFG / "uc7_config.yaml", mode="r") as uc7_config:
|
||||
cfg = yaml.safe_load(uc7_config)
|
||||
|
||||
for a in cfg["agents"]:
|
||||
if a["ref"] == "attacker":
|
||||
tap_cfg = a
|
||||
|
||||
tap_cfg["agent_settings"]["start_step"] = 1
|
||||
tap_cfg["agent_settings"]["frequency"] = 5
|
||||
tap_cfg["agent_settings"]["variance"] = 0
|
||||
|
||||
env = PrimaiteGymEnv(env_config=cfg)
|
||||
|
||||
return env
|
||||
|
||||
|
||||
def uc7_tap003_env(**kwargs) -> PrimaiteGymEnv:
|
||||
"""Setups the UC7 TAP003 Game with the following settings:
|
||||
|
||||
start_step = Start on Step 1
|
||||
frequency = Attack Every 5 Steps
|
||||
|
||||
Each PyTest will define the rest of the TAP & Kill Chain settings via **Kwargs
|
||||
"""
|
||||
with open(_EXAMPLE_CFG / "uc7_config_tap003.yaml", "r") as uc7_config:
|
||||
cfg = yaml.safe_load(uc7_config)
|
||||
|
||||
for a in cfg["agents"]:
|
||||
if a["ref"] == "attacker":
|
||||
tap_cfg = a
|
||||
|
||||
tap_cfg["agent_settings"]["start_step"] = 1
|
||||
tap_cfg["agent_settings"]["frequency"] = 5
|
||||
tap_cfg["agent_settings"]["variance"] = 0
|
||||
|
||||
if "repeat_kill_chain" in kwargs:
|
||||
tap_cfg["agent_settings"]["repeat_kill_chain"] = kwargs["repeat_kill_chain"]
|
||||
if "repeat_kill_chain_stages" in kwargs:
|
||||
tap_cfg["agent_settings"]["repeat_kill_chain_stages"] = kwargs["repeat_kill_chain_stages"]
|
||||
if "planning_probability" in kwargs:
|
||||
tap_cfg["agent_settings"]["kill_chain"]["PLANNING"]["probability"] = kwargs["planning_probability"]
|
||||
if "custom_kill_chain" in kwargs:
|
||||
tap_cfg["agent_settings"]["kill_chain"] = kwargs["custom_kill_chain"]
|
||||
if "starting_nodes" in kwargs:
|
||||
tap_cfg["agent_settings"]["starting_nodes"] = kwargs["starting_nodes"]
|
||||
if "target_nodes" in kwargs:
|
||||
tap_cfg["agent_settings"]["target_nodes"] = kwargs["target_nodes"]
|
||||
|
||||
env = PrimaiteGymEnv(env_config=cfg)
|
||||
return env
|
||||
|
||||
|
||||
def test_tap001_setup():
|
||||
"""Tests abstract TAP's following methods:
|
||||
1. _setup_kill_chain
|
||||
2. _setup_agent_kill_chain
|
||||
3. _setup_tap_applications
|
||||
"""
|
||||
env = uc7_tap001_env() # Using TAP001 for PyTests.
|
||||
tap: TAP001 = env.game.agents["attacker"]
|
||||
|
||||
# check the kill chain loaded correctly
|
||||
assert tap.selected_kill_chain is MobileMalwareKillChain
|
||||
assert tap.selected_kill_chain.FAILED == BaseKillChain.FAILED
|
||||
assert tap.selected_kill_chain.SUCCEEDED == BaseKillChain.SUCCEEDED
|
||||
assert tap.selected_kill_chain.NOT_STARTED == BaseKillChain.NOT_STARTED
|
||||
|
||||
if sn := tap.config.agent_settings.default_starting_node:
|
||||
assert tap.starting_node == sn
|
||||
else:
|
||||
assert tap.starting_node in tap.config.agent_settings.starting_nodes
|
||||
|
||||
if ti := tap.config.agent_settings.default_target_ip:
|
||||
assert tap.target_ip == ti
|
||||
else:
|
||||
assert tap.target_ip in tap.config.agent_settings.target_ips
|
||||
|
||||
assert tap.next_execution_timestep == tap.config.agent_settings.start_step
|
||||
|
||||
assert tap.current_host == tap.starting_node
|
||||
|
||||
|
||||
def test_abstract_tap_select_start_node():
|
||||
"""Tests that Abstract TAP's _select_start_node"""
|
||||
env = uc7_tap003_env(repeat_kill_chain=True, repeat_kill_chain_stages=True) # Using TAP003 for PyTests.
|
||||
tap: TAP003 = env.game.agents["attacker"]
|
||||
|
||||
assert tap.starting_node == "ST-PROJ-A-PRV-PC-1"
|
||||
assert tap.current_host == tap.starting_node
|
||||
|
||||
|
||||
def test_outcome_handler():
|
||||
"""Tests Abstract Tap's outcome handler concludes the episode when the kill chain fails."""
|
||||
env = uc7_tap003_env(repeat_kill_chain=False, repeat_kill_chain_stages=False) # Using TAP003 for PyTests.
|
||||
tap: TAP003 = env.game.agents["attacker"]
|
||||
tap.current_kill_chain_stage = BaseKillChain.FAILED
|
||||
|
||||
for _ in range(6):
|
||||
env.step(0)
|
||||
assert tap.actions_concluded == True
|
||||
|
||||
|
||||
def test_abstract_tap_kill_chain_repeat():
|
||||
"""Tests that the kill chain repeats from the beginning upon failure."""
|
||||
env = uc7_tap003_env(repeat_kill_chain=True, repeat_kill_chain_stages=False) # Using TAP003 for PyTests.
|
||||
tap: TAP003 = env.game.agents["attacker"]
|
||||
for _ in range(15):
|
||||
env.step(0) # Steps directly to the Access Stage
|
||||
assert tap.current_kill_chain_stage == InsiderKillChain.ACCESS
|
||||
tap.current_kill_chain_stage = BaseKillChain.FAILED
|
||||
for _ in range(5):
|
||||
env.step(0) # Steps to manipulation - but failure causes the kill chain to restart.
|
||||
assert tap.actions_concluded == False
|
||||
assert tap.current_kill_chain_stage == InsiderKillChain.RECONNAISSANCE
|
||||
|
||||
"""Tests that kill chain stages repeat when expected"""
|
||||
env = uc7_tap003_env(
|
||||
repeat_kill_chain=True, repeat_kill_chain_stages=True, planning_probability=0
|
||||
) # Using TAP003 for PyTests.
|
||||
tap: TAP003 = env.game.agents["attacker"]
|
||||
tap.current_kill_chain_stage = InsiderKillChain.PLANNING
|
||||
for _ in range(15):
|
||||
env.step(0) # Attempts to progress past the PLANNING stage multiple times.
|
||||
assert tap.actions_concluded == False
|
||||
assert tap.current_kill_chain_stage == InsiderKillChain.PLANNING
|
||||
@@ -0,0 +1,69 @@
|
||||
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
from primaite.config.load import _EXAMPLE_CFG
|
||||
from primaite.game.agent.scripted_agents.abstract_tap import (
|
||||
AbstractTAP,
|
||||
BaseKillChain,
|
||||
KillChainOptions,
|
||||
KillChainStageOptions,
|
||||
KillChainStageProgress,
|
||||
)
|
||||
from primaite.game.agent.scripted_agents.TAP001 import MobileMalwareKillChain
|
||||
from primaite.game.agent.scripted_agents.TAP003 import InsiderKillChain
|
||||
from primaite.session.environment import PrimaiteGymEnv
|
||||
|
||||
# Defining constants.
|
||||
|
||||
START_STEP = 1 # The starting step of the agent.
|
||||
FREQUENCY = 2 # The frequency of kill chain stage progression (E.g it's next attempt at "attacking").
|
||||
VARIANCE = 0 # The timestep variance between kill chain progression (E.g Next timestep = Frequency +/- variance)
|
||||
|
||||
|
||||
def uc7_tap003_env() -> PrimaiteGymEnv:
|
||||
"""Setups the UC7 TAP003 Game with the start_step & frequency set to 1 with probabilities set to 1 as well"""
|
||||
with open(_EXAMPLE_CFG / "uc7_config_tap003.yaml", mode="r") as uc7_config:
|
||||
cfg = yaml.safe_load(uc7_config)
|
||||
cfg["io_settings"]["save_sys_logs"] = False
|
||||
cfg["agents"][32]["agent_settings"]["start_step"] = START_STEP
|
||||
cfg["agents"][32]["agent_settings"]["frequency"] = FREQUENCY
|
||||
cfg["agents"][32]["agent_settings"]["variance"] = VARIANCE
|
||||
env = PrimaiteGymEnv(env_config=cfg)
|
||||
return env
|
||||
|
||||
|
||||
def uc7_tap001_env() -> PrimaiteGymEnv:
|
||||
"""Setup the UC7 TAP001 Game with the start_step & frequency set to 1 & 2 respectively. Probabilities are set to 1"""
|
||||
with open(_EXAMPLE_CFG / "uc7_config.yaml", mode="r") as uc7_config:
|
||||
cfg = yaml.safe_load(uc7_config)
|
||||
cfg["io_settings"]["save_sys_logs"] = False
|
||||
cfg["agents"][32]["agent_settings"]["start_step"] = START_STEP
|
||||
cfg["agents"][32]["agent_settings"]["frequency"] = FREQUENCY
|
||||
cfg["agents"][32]["agent_settings"]["variance"] = VARIANCE
|
||||
env = PrimaiteGymEnv(env_config=cfg)
|
||||
return env
|
||||
|
||||
|
||||
def test_tap003_insider_kill_chain_load():
|
||||
"""Tests that tap003's insider kill chain is successfully loaded into the tap.selected_kill_chain attribute."""
|
||||
env = uc7_tap003_env() # Using TAP003 for PyTests.
|
||||
tap: TAP003 = env.game.agents["attacker"]
|
||||
# Asserting that the Base Kill Chain intEnum stages are loaded
|
||||
assert BaseKillChain.FAILED in [enums for enums in tap.selected_kill_chain]
|
||||
assert BaseKillChain.SUCCEEDED in [enums for enums in tap.selected_kill_chain]
|
||||
assert BaseKillChain.NOT_STARTED in [enums for enums in tap.selected_kill_chain]
|
||||
# Asserting that the Insider Kill Chain intenum stages are loaded.
|
||||
assert len(InsiderKillChain.__members__) == len(tap.selected_kill_chain.__members__)
|
||||
|
||||
|
||||
def test_tap001_mobile_malware_kill_chain_load():
|
||||
"""Tests that tap001's mobile malware is successfully loaded into the tap.selected_kill_chain attribute."""
|
||||
env = uc7_tap001_env() # Using TAP003 for PyTests.
|
||||
tap: TAP001 = env.game.agents["attacker"]
|
||||
# Asserting that the Base Kill Chain intEnum stages are loaded.
|
||||
assert BaseKillChain.FAILED in [enums for enums in tap.selected_kill_chain]
|
||||
assert BaseKillChain.SUCCEEDED in [enums for enums in tap.selected_kill_chain]
|
||||
assert BaseKillChain.NOT_STARTED in [enums for enums in tap.selected_kill_chain]
|
||||
# Asserting that the Insider Kill Chain intEnum stages are loaded.
|
||||
assert len(MobileMalwareKillChain.__members__) == len(tap.selected_kill_chain.__members__)
|
||||
@@ -0,0 +1,109 @@
|
||||
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
from primaite.config.load import _EXAMPLE_CFG
|
||||
from primaite.game.agent.scripted_agents.abstract_tap import (
|
||||
AbstractTAP,
|
||||
BaseKillChain,
|
||||
KillChainOptions,
|
||||
KillChainStageOptions,
|
||||
KillChainStageProgress,
|
||||
)
|
||||
from primaite.game.agent.scripted_agents.TAP001 import MobileMalwareKillChain
|
||||
from primaite.game.agent.scripted_agents.TAP003 import InsiderKillChain
|
||||
from primaite.session.environment import PrimaiteGymEnv
|
||||
|
||||
# Defining constants.
|
||||
|
||||
START_STEP = 1 # The starting step of the agent.
|
||||
FREQUENCY = 2 # The frequency of kill chain stage progression (E.g it's next attempt at "attacking").
|
||||
VARIANCE = 0 # The timestep variance between kill chain progression (E.g Next timestep = Frequency +/- variance)
|
||||
|
||||
|
||||
def uc7_tap001_env(**kwargs) -> PrimaiteGymEnv:
|
||||
"""Setups the UC7 tap001 Game with the start_step & frequency set to 1 with probabilities set to 1 as well"""
|
||||
with open(_EXAMPLE_CFG / "uc7_config.yaml", mode="r") as uc7_config:
|
||||
cfg = yaml.safe_load(uc7_config)
|
||||
cfg["io_settings"]["save_sys_logs"] = False
|
||||
cfg["agents"][32]["agent_settings"]["start_step"] = START_STEP
|
||||
cfg["agents"][32]["agent_settings"]["frequency"] = FREQUENCY
|
||||
cfg["agents"][32]["agent_settings"]["variance"] = VARIANCE
|
||||
cfg["agents"][32]["agent_settings"]["repeat_kill_chain"] = kwargs["repeat_kill_chain"]
|
||||
cfg["agents"][32]["agent_settings"]["repeat_kill_chain_stages"] = kwargs["repeat_kill_chain_stages"]
|
||||
cfg["agents"][32]["agent_settings"]["kill_chain"]["PROPAGATE"]["probability"] = kwargs["propagate_probability"]
|
||||
cfg["agents"][32]["agent_settings"]["kill_chain"]["PAYLOAD"]["probability"] = kwargs["payload_probability"]
|
||||
env = PrimaiteGymEnv(env_config=cfg)
|
||||
return env
|
||||
|
||||
|
||||
def test_tap001_repeating_kill_chain():
|
||||
"""Tests to check that tap001 repeats it's kill chain after success"""
|
||||
env = uc7_tap001_env(
|
||||
repeat_kill_chain=True,
|
||||
repeat_kill_chain_stages=True,
|
||||
payload_probability=1,
|
||||
propagate_probability=1,
|
||||
)
|
||||
tap001: TAP001 = env.game.agents["attacker"]
|
||||
# Looping for 50 timesteps - As the agent is set to execute an action every 2 timesteps
|
||||
# This is the equivalent of the agent taking 20 actions.
|
||||
for _ in range(50): # This for loop should never actually fully complete.
|
||||
if tap001.current_kill_chain_stage == BaseKillChain.SUCCEEDED:
|
||||
break
|
||||
env.step(0)
|
||||
|
||||
# Catches if the above for loop fully completes.
|
||||
# This test uses a probability of 1 for all stages and a variance of 2 timesteps
|
||||
# Thus the for loop above should never fail.
|
||||
# If this occurs then there is an error somewhere in either:
|
||||
# 1. The TAP Logic
|
||||
# 2. Failing Agent Actions are causing the TAP to fail. (See tap_return_handler).
|
||||
if tap001.current_kill_chain_stage != BaseKillChain.SUCCEEDED:
|
||||
pytest.fail("Attacker Never Reached SUCCEEDED - Please evaluate current TAP Logic.")
|
||||
|
||||
# Stepping twice for the succeeded logic to kick in:
|
||||
env.step(0)
|
||||
env.step(0)
|
||||
env.step(0)
|
||||
env.step(0)
|
||||
|
||||
assert tap001.current_kill_chain_stage.name == MobileMalwareKillChain.DOWNLOAD.name
|
||||
assert tap001.next_kill_chain_stage.name == MobileMalwareKillChain.INSTALL.name
|
||||
|
||||
|
||||
def test_tap001_repeating_kill_chain_stages():
|
||||
"""Tests to check that tap001 repeats it's kill chain after failing a kill chain stage."""
|
||||
env = uc7_tap001_env(
|
||||
repeat_kill_chain=True,
|
||||
repeat_kill_chain_stages=True,
|
||||
payload_probability=1,
|
||||
propagate_probability=0,
|
||||
# Probability 0 = Will never be able to perform the access stage and progress to Manipulation.
|
||||
)
|
||||
tap001: TAP001 = env.game.agents["attacker"]
|
||||
env.step(0) # Skipping not started
|
||||
env.step(0) # Successful on the first stage
|
||||
assert tap001.current_kill_chain_stage.name == MobileMalwareKillChain.DOWNLOAD.name
|
||||
assert tap001.next_kill_chain_stage.name == MobileMalwareKillChain.INSTALL.name
|
||||
env.step(0) # Successful progression to the second stage
|
||||
env.step(0)
|
||||
env.step(0)
|
||||
env.step(0)
|
||||
assert tap001.current_kill_chain_stage.name == MobileMalwareKillChain.INSTALL.name
|
||||
assert tap001.next_kill_chain_stage.name == MobileMalwareKillChain.ACTIVATE.name
|
||||
env.step(0) # Successful progression to the third stage
|
||||
env.step(0)
|
||||
assert tap001.current_kill_chain_stage.name == MobileMalwareKillChain.ACTIVATE.name
|
||||
assert tap001.next_kill_chain_stage.name == MobileMalwareKillChain.PROPAGATE.name
|
||||
env.step(0) # Successful progression to the Fourth stage
|
||||
env.step(0)
|
||||
env.step(0)
|
||||
env.step(0)
|
||||
assert tap001.current_kill_chain_stage.name == MobileMalwareKillChain.PROPAGATE.name
|
||||
assert tap001.next_kill_chain_stage.name == MobileMalwareKillChain.COMMAND_AND_CONTROL.name
|
||||
env.step(0) # FAILURE -- Unsuccessful progression to the Fourth stage
|
||||
env.step(0)
|
||||
assert tap001.current_kill_chain_stage.name == MobileMalwareKillChain.PROPAGATE.name
|
||||
assert tap001.next_kill_chain_stage.name == MobileMalwareKillChain.COMMAND_AND_CONTROL.name
|
||||
assert tap001.current_stage_progress == KillChainStageProgress.PENDING
|
||||
@@ -0,0 +1,215 @@
|
||||
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
from primaite.config.load import _EXAMPLE_CFG
|
||||
from primaite.game.agent.scripted_agents.abstract_tap import (
|
||||
AbstractTAP,
|
||||
BaseKillChain,
|
||||
KillChainOptions,
|
||||
KillChainStageOptions,
|
||||
KillChainStageProgress,
|
||||
)
|
||||
from primaite.game.agent.scripted_agents.TAP001 import MobileMalwareKillChain
|
||||
from primaite.game.agent.scripted_agents.TAP003 import InsiderKillChain
|
||||
from primaite.session.environment import PrimaiteGymEnv
|
||||
from primaite.simulator.file_system.file_system_item_abc import FileSystemItemHealthStatus
|
||||
from primaite.simulator.system.applications.red_applications.c2.c2_beacon import C2Beacon
|
||||
from primaite.simulator.system.services.database.database_service import DatabaseService
|
||||
|
||||
# Defining constants.
|
||||
|
||||
START_STEP = 1 # The starting step of the agent.
|
||||
FREQUENCY = 2 # The frequency of kill chain stage progression (E.g it's next attempt at "attacking").
|
||||
VARIANCE = 0 # The timestep variance between kill chain progression (E.g Next timestep = Frequency +/- variance)
|
||||
|
||||
# Defining constants.
|
||||
|
||||
START_STEP = 1 # The starting step of the agent.
|
||||
FREQUENCY = 2 # The frequency of kill chain stage progression (E.g it's next attempt at "attacking").
|
||||
VARIANCE = 0 # The timestep variance between kill chain progression (E.g Next timestep = Frequency +/- variance)
|
||||
REPEAT_KILL_CHAIN = False # Should the TAP repeat the kill chain after success/failure?
|
||||
REPEAT_KILL_CHAIN_STAGES = False # Should the TAP restart from it's previous stage on failure?
|
||||
KILL_CHAIN_PROBABILITY = 1 # Blank probability for agent 'success'
|
||||
DATA_EXFIL = True # Data exfiltration on the payload stage is enabled.
|
||||
|
||||
|
||||
def uc7_tap001_env() -> PrimaiteGymEnv:
|
||||
"""Setups the UC7 tap001 Game with the start_step & frequency set to 1 with probabilities set to 1 as well"""
|
||||
with open(_EXAMPLE_CFG / "uc7_config.yaml", mode="r") as uc7_config:
|
||||
cfg = yaml.safe_load(uc7_config)
|
||||
cfg["io_settings"]["save_sys_logs"] = False
|
||||
cfg["agents"][32]["agent_settings"]["start_step"] = START_STEP
|
||||
cfg["agents"][32]["agent_settings"]["frequency"] = FREQUENCY
|
||||
cfg["agents"][32]["agent_settings"]["variance"] = VARIANCE
|
||||
cfg["agents"][32]["agent_settings"]["repeat_kill_chain"] = REPEAT_KILL_CHAIN_STAGES
|
||||
cfg["agents"][32]["agent_settings"]["repeat_kill_chain_stages"] = REPEAT_KILL_CHAIN_STAGES
|
||||
cfg["agents"][32]["agent_settings"]["kill_chain"]["PAYLOAD"]["probability"] = KILL_CHAIN_PROBABILITY
|
||||
cfg["agents"][32]["agent_settings"]["kill_chain"]["PROPAGATE"]["probability"] = KILL_CHAIN_PROBABILITY
|
||||
cfg["agents"][32]["agent_settings"]["kill_chain"]["PAYLOAD"]["exfiltrate"] = DATA_EXFIL
|
||||
env = PrimaiteGymEnv(env_config=cfg)
|
||||
return env
|
||||
|
||||
|
||||
def test_tap001_kill_chain_stage_DOWNLOAD():
|
||||
"""Tests that the DOWNLOAD Mobile Malware step works as expected and the expected impacts are made in the simulation."""
|
||||
|
||||
# Instantiating the relevant simulation/game objects:
|
||||
env = uc7_tap001_env()
|
||||
tap001: TAP001 = env.game.agents["attacker"]
|
||||
starting_host = env.game.simulation.network.get_node_by_hostname(tap001.starting_node)
|
||||
assert tap001.current_kill_chain_stage == BaseKillChain.NOT_STARTED
|
||||
|
||||
# Frequency is set to two steps
|
||||
env.step(0)
|
||||
env.step(0)
|
||||
|
||||
env.step(0)
|
||||
env.step(0)
|
||||
|
||||
assert tap001.current_kill_chain_stage.name == MobileMalwareKillChain.DOWNLOAD.name
|
||||
assert tap001.next_kill_chain_stage.name == MobileMalwareKillChain.INSTALL.name
|
||||
assert tap001.current_stage_progress == KillChainStageProgress.IN_PROGRESS
|
||||
|
||||
# Creating the "downloads" folder
|
||||
env.step(0)
|
||||
env.step(0)
|
||||
|
||||
assert starting_host.software_manager.file_system.get_folder(folder_name="downloads")
|
||||
assert starting_host.software_manager.file_system.get_file(folder_name="downloads", file_name="malware_dropper.ps1")
|
||||
|
||||
# Testing that the num_file_increase works
|
||||
|
||||
assert starting_host.file_system.num_file_creations == 1
|
||||
|
||||
|
||||
def test_tap001_kill_chain_stage_INSTALL():
|
||||
"""Tests that the INSTALL Mobile Malware step works as expected and the expected impacts are made in the simulation."""
|
||||
env = uc7_tap001_env()
|
||||
tap001: TAP001 = env.game.agents["attacker"]
|
||||
# The tap001's Starting Client:
|
||||
starting_host = env.game.simulation.network.get_node_by_hostname(tap001.starting_node)
|
||||
|
||||
# Skipping directly to the activate stage
|
||||
for _ in range(6):
|
||||
env.step(0)
|
||||
|
||||
# Testing that tap001 Enters into the expected kill chain stages
|
||||
assert tap001.current_kill_chain_stage.name == MobileMalwareKillChain.INSTALL.name
|
||||
assert tap001.next_kill_chain_stage.name == MobileMalwareKillChain.ACTIVATE.name
|
||||
|
||||
env.step(0) # Allows the agent action to resolve.
|
||||
env.step(0)
|
||||
|
||||
ransomware_dropper_file = starting_host.software_manager.file_system.get_file(
|
||||
folder_name="downloads", file_name="malware_dropper.ps1"
|
||||
)
|
||||
assert ransomware_dropper_file.num_access == 1
|
||||
|
||||
|
||||
def test_tap001_kill_chain_stage_ACTIVATE():
|
||||
"""Tests that the ACTIVATE Mobile Malware step works as expected and the current impacts are made in the simulation."""
|
||||
env = uc7_tap001_env()
|
||||
tap001: TAP001 = env.game.agents["attacker"]
|
||||
# The tap001's Starting Client:
|
||||
starting_host = env.game.simulation.network.get_node_by_hostname(tap001.starting_node)
|
||||
|
||||
# Skipping directly to the activate stage
|
||||
for _ in range(8):
|
||||
env.step(0)
|
||||
|
||||
assert tap001.current_kill_chain_stage.name == MobileMalwareKillChain.ACTIVATE.name
|
||||
assert tap001.next_kill_chain_stage.name == MobileMalwareKillChain.PROPAGATE.name
|
||||
|
||||
# Installing ransomware-script Application
|
||||
env.step(0)
|
||||
env.step(0)
|
||||
|
||||
# Installing NMAP Application
|
||||
env.step(0)
|
||||
env.step(0)
|
||||
|
||||
# These asserts will fail if the applications are not present in the software_manager
|
||||
assert starting_host.software_manager.software["ransomware-script"]
|
||||
assert starting_host.software_manager.software["nmap"]
|
||||
|
||||
|
||||
def test_tap001_kill_chain_stage_PROPAGATE():
|
||||
"""Tests that the ACTIVATE Mobile Malware step works as expected and the current impacts are made in the simulation."""
|
||||
env = uc7_tap001_env()
|
||||
tap001: TAP001 = env.game.agents["attacker"]
|
||||
|
||||
for _ in range(12):
|
||||
env.step(0)
|
||||
|
||||
assert tap001.current_kill_chain_stage.name == MobileMalwareKillChain.PROPAGATE.name
|
||||
assert tap001.next_kill_chain_stage.name == MobileMalwareKillChain.COMMAND_AND_CONTROL.name
|
||||
|
||||
# Specific Stage by Stage Propagate Testing is done in test_tap001_propagate.
|
||||
fail_safe_var = 0
|
||||
while tap001.current_kill_chain_stage.name == MobileMalwareKillChain.PROPAGATE:
|
||||
env.step(0)
|
||||
assert tap001.current_stage_progress == KillChainStageProgress.IN_PROGRESS
|
||||
fail_safe_var += 1
|
||||
if fail_safe_var == 100:
|
||||
pytest.fail("Fail Safe Variable was hit! -- Propagate step is running indefinitely")
|
||||
|
||||
|
||||
def test_tap001_kill_chain_stage_COMMAND_AND_CONTROL():
|
||||
"""Tests that the Command And Control Mobile Malware step works as expected and the current impacts are made in the simulation."""
|
||||
env = uc7_tap001_env()
|
||||
tap001: TAP001 = env.game.agents["attacker"]
|
||||
fail_safe_var = 0
|
||||
|
||||
for _ in range(28):
|
||||
env.step(0)
|
||||
|
||||
assert tap001.current_kill_chain_stage.name == MobileMalwareKillChain.COMMAND_AND_CONTROL.name
|
||||
assert tap001.next_kill_chain_stage.name == MobileMalwareKillChain.PAYLOAD.name
|
||||
|
||||
while tap001.current_kill_chain_stage == MobileMalwareKillChain.COMMAND_AND_CONTROL:
|
||||
env.step(0)
|
||||
fail_safe_var += 1
|
||||
env.game.simulation.network.airspace.show()
|
||||
if fail_safe_var == 100:
|
||||
pytest.fail(reason="Fail Safe Variable was hit! -- Propagate step is running indefinitely")
|
||||
|
||||
starting_host = env.game.simulation.network.get_node_by_hostname(tap001.starting_node)
|
||||
|
||||
c2_beacon: C2Beacon = starting_host.software_manager.software["c2-beacon"]
|
||||
|
||||
assert c2_beacon.c2_connection_active is True
|
||||
|
||||
|
||||
def test_tap001_kill_chain_stage_PAYLOAD():
|
||||
"""Tests that the PAYLOAD Mobile Malware step works as expected and the current impacts are made in the simulation."""
|
||||
|
||||
env = uc7_tap001_env()
|
||||
tap001: TAP001 = env.game.agents["attacker"]
|
||||
|
||||
# The tap001's Target Database
|
||||
target_host = env.game.simulation.network.get_node_by_hostname("ST-DATA-PRV-SRV-DB")
|
||||
db_server_service: DatabaseService = target_host.software_manager.software.get("database-service")
|
||||
|
||||
# Green agent status requests are tested within the ransomware application tests.
|
||||
# See test_ransomware_disrupts_green_agent_connection for further reference.
|
||||
assert db_server_service.db_file.health_status is FileSystemItemHealthStatus.GOOD
|
||||
|
||||
fail_safe_var = 0
|
||||
while tap001.current_kill_chain_stage != MobileMalwareKillChain.PAYLOAD:
|
||||
env.step(0)
|
||||
fail_safe_var += 1
|
||||
if fail_safe_var == 100:
|
||||
pytest.fail(reason="Fail Safe Variable was hit! -- a step is running indefinitely")
|
||||
|
||||
for _ in range(12):
|
||||
env.step(0)
|
||||
|
||||
assert db_server_service.db_file.health_status is FileSystemItemHealthStatus.CORRUPT
|
||||
|
||||
# Asserting we've managed to the database.db file onto the starting node & server
|
||||
starting_host = env.game.simulation.network.get_node_by_hostname(tap001.starting_node)
|
||||
c2_host = env.game.simulation.network.get_node_by_hostname(tap001.c2_settings["c2_server"])
|
||||
|
||||
assert starting_host.file_system.access_file(folder_name="exfiltration_folder", file_name="database.db")
|
||||
assert c2_host.file_system.access_file(folder_name="exfiltration_folder", file_name="database.db")
|
||||
@@ -0,0 +1,140 @@
|
||||
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
from primaite.config.load import _EXAMPLE_CFG
|
||||
from primaite.game.agent.scripted_agents.abstract_tap import (
|
||||
AbstractTAP,
|
||||
BaseKillChain,
|
||||
KillChainOptions,
|
||||
KillChainStageOptions,
|
||||
KillChainStageProgress,
|
||||
)
|
||||
from primaite.game.agent.scripted_agents.TAP001 import MobileMalwareKillChain
|
||||
from primaite.game.agent.scripted_agents.TAP003 import InsiderKillChain
|
||||
from primaite.session.environment import PrimaiteGymEnv
|
||||
from primaite.simulator.file_system.file_system_item_abc import FileSystemItemHealthStatus
|
||||
from primaite.simulator.system.applications.red_applications.c2.c2_beacon import C2Beacon
|
||||
from primaite.simulator.system.services.database.database_service import DatabaseService
|
||||
|
||||
# Defining generic tap constants.
|
||||
|
||||
START_STEP = 1 # The starting step of the agent.
|
||||
FREQUENCY = 2 # The frequency of kill chain stage progression (E.g it's next attempt at "attacking").
|
||||
VARIANCE = 0 # The timestep variance between kill chain progression (E.g Next timestep = Frequency +/- variance)
|
||||
REPEAT_KILL_CHAIN = False # Should the TAP repeat the kill chain after success/failure?
|
||||
REPEAT_KILL_CHAIN_STAGES = False # Should the TAP restart from it's previous stage on failure?
|
||||
KILL_CHAIN_PROBABILITY = 1 # Blank probability for agent 'success'
|
||||
|
||||
|
||||
def uc7_tap001_env(**kwargs) -> PrimaiteGymEnv:
|
||||
"""Setups the UC7 tap001 Game with the start_step & frequency set to 1 with probabilities set to 1 as well"""
|
||||
with open(_EXAMPLE_CFG / "uc7_config.yaml", mode="r") as uc7_config:
|
||||
cfg = yaml.safe_load(uc7_config)
|
||||
cfg["io_settings"]["save_sys_logs"] = False
|
||||
agent_cfg = cfg["agents"][32]["agent_settings"]
|
||||
agent_cfg["start_step"] = START_STEP
|
||||
agent_cfg["frequency"] = FREQUENCY
|
||||
agent_cfg["variance"] = VARIANCE
|
||||
agent_cfg["repeat_kill_chain"] = REPEAT_KILL_CHAIN_STAGES
|
||||
agent_cfg["repeat_kill_chain_stages"] = REPEAT_KILL_CHAIN_STAGES
|
||||
agent_cfg["kill_chain"]["PAYLOAD"]["probability"] = KILL_CHAIN_PROBABILITY
|
||||
agent_cfg["kill_chain"]["PROPAGATE"]["probability"] = KILL_CHAIN_PROBABILITY
|
||||
agent_cfg["kill_chain"]["PROPAGATE"]["scan_attempts"] = kwargs["scan_attempts"]
|
||||
agent_cfg["kill_chain"]["PAYLOAD"]["payload"] = kwargs["payload"]
|
||||
agent_cfg["kill_chain"]["PROPAGATE"]["network_addresses"] = kwargs["network_addresses"]
|
||||
if "repeat_scan" in kwargs:
|
||||
agent_cfg["kill_chain"]["PROPAGATE"]["repeat_scan"] = kwargs["repeat_scan"]
|
||||
if "starting_nodes" in kwargs:
|
||||
agent_cfg["starting_nodes"] = kwargs["starting_nodes"]
|
||||
agent_cfg["default_starting_node"] = kwargs["starting_nodes"][0]
|
||||
if "target_ips" in kwargs:
|
||||
agent_cfg["target_ips"] = kwargs["target_ips"]
|
||||
env = PrimaiteGymEnv(env_config=cfg)
|
||||
return env
|
||||
|
||||
|
||||
def test_tap001_kill_chain_stage_PROPAGATE_default():
|
||||
"""Tests that the PROPAGATE Mobile Malware step works as expected and the current impacts are made in the simulation."""
|
||||
payload = "ENCRYPT"
|
||||
scan_attempts = 10
|
||||
network_addresses = [
|
||||
"192.168.230.0/29",
|
||||
"192.168.10.0/26",
|
||||
"192.168.20.0/30",
|
||||
"192.168.240.0/29",
|
||||
"192.168.220.0/29",
|
||||
]
|
||||
env = uc7_tap001_env(payload=payload, scan_attempts=scan_attempts, network_addresses=network_addresses)
|
||||
tap001: TAP001 = env.game.agents["attacker"]
|
||||
|
||||
# First Kill Chain Stages
|
||||
for _ in range(12):
|
||||
env.step(0)
|
||||
|
||||
# Assert that we're about to enter into the propagate stage.
|
||||
assert tap001.current_kill_chain_stage.name == MobileMalwareKillChain.PROPAGATE.name
|
||||
assert tap001.next_kill_chain_stage.name == MobileMalwareKillChain.COMMAND_AND_CONTROL.name
|
||||
|
||||
# Move into the propagate stage.
|
||||
while tap001.current_kill_chain_stage == MobileMalwareKillChain.PROPAGATE:
|
||||
env.step(0)
|
||||
|
||||
# Assert that we've successfully moved into the command and control stage.
|
||||
assert tap001.current_kill_chain_stage.name == MobileMalwareKillChain.COMMAND_AND_CONTROL.name
|
||||
assert tap001.next_kill_chain_stage.name == MobileMalwareKillChain.PAYLOAD.name
|
||||
|
||||
|
||||
def test_tap001_kill_chain_stage_PROPAGATE_different_starting_node():
|
||||
"""Tests that the PROPAGATE Mobile Malware step works as expected and the current impacts are made in the simulation from a different starting node."""
|
||||
payload = "ENCRYPT"
|
||||
scan_attempts = 10
|
||||
network_addresses = [
|
||||
"192.168.230.0/29",
|
||||
"192.168.10.0/26",
|
||||
"192.168.20.0/30",
|
||||
"192.168.240.0/29",
|
||||
"192.168.220.0/29",
|
||||
]
|
||||
starting_nodes = ["ST-PROJ-B-PRV-PC-2", "ST-PROJ-C-PRV-PC-3"]
|
||||
|
||||
env = uc7_tap001_env(
|
||||
payload=payload, scan_attempts=scan_attempts, network_addresses=network_addresses, starting_nodes=starting_nodes
|
||||
)
|
||||
|
||||
tap001: TAP001 = env.game.agents["attacker"]
|
||||
|
||||
for _ in range(12):
|
||||
env.step(0)
|
||||
|
||||
assert tap001.current_kill_chain_stage.name == MobileMalwareKillChain.PROPAGATE.name
|
||||
assert tap001.next_kill_chain_stage.name == MobileMalwareKillChain.COMMAND_AND_CONTROL.name
|
||||
|
||||
# Specific Stage by Stage Propagate Testing is done in test_tap001_propagate.
|
||||
while tap001.current_kill_chain_stage == MobileMalwareKillChain.PROPAGATE:
|
||||
env.step(0)
|
||||
|
||||
assert tap001.current_kill_chain_stage.name == MobileMalwareKillChain.COMMAND_AND_CONTROL.name
|
||||
assert tap001.next_kill_chain_stage.name == MobileMalwareKillChain.PAYLOAD.name
|
||||
|
||||
|
||||
def test_tap001_kill_chain_stage_PROPAGATE_repeat_scan():
|
||||
"""Tests that the PROPAGATE Mobile Malware step will fail when the target is unable to be located."""
|
||||
payload = "ENCRYPT"
|
||||
scan_attempts = 20
|
||||
repeat_scan = True
|
||||
network_addresses = ["192.168.1.0/24", "192.168.0.0/28", "100.64.0.0/30", "172.168.0.0/28"]
|
||||
env = uc7_tap001_env(
|
||||
payload=payload, scan_attempts=scan_attempts, network_addresses=network_addresses, repeat_scan=repeat_scan
|
||||
)
|
||||
for _ in range(12):
|
||||
env.step(0)
|
||||
|
||||
tap001: TAP001 = env.game.agents["attacker"]
|
||||
|
||||
while tap001.current_kill_chain_stage == MobileMalwareKillChain.PROPAGATE:
|
||||
env.step(0)
|
||||
|
||||
# As the given network_address does not contain the target, we should failed because the maximum amount of scan attempts has been reached
|
||||
assert tap001.scans_complete == 20
|
||||
assert tap001.current_kill_chain_stage == MobileMalwareKillChain.FAILED
|
||||
@@ -0,0 +1,101 @@
|
||||
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
from primaite.config.load import _EXAMPLE_CFG
|
||||
from primaite.game.agent.scripted_agents.abstract_tap import (
|
||||
AbstractTAP,
|
||||
BaseKillChain,
|
||||
KillChainOptions,
|
||||
KillChainStageOptions,
|
||||
KillChainStageProgress,
|
||||
)
|
||||
from primaite.game.agent.scripted_agents.TAP001 import MobileMalwareKillChain
|
||||
from primaite.game.agent.scripted_agents.TAP003 import InsiderKillChain
|
||||
from primaite.session.environment import PrimaiteGymEnv
|
||||
|
||||
# Defining constants.
|
||||
|
||||
START_STEP = 1 # The starting step of the agent.
|
||||
FREQUENCY = 2 # The frequency of kill chain stage progression (E.g it's next attempt at "attacking").
|
||||
VARIANCE = 0 # The timestep variance between kill chain progression (E.g Next timestep = Frequency +/- variance)
|
||||
|
||||
|
||||
def uc7_tap003_env(**kwargs) -> PrimaiteGymEnv:
|
||||
"""Setups the UC7 TAP003 Game with the start_step & frequency set to 1 with probabilities set to 1 as well"""
|
||||
with open(_EXAMPLE_CFG / "uc7_config_tap003.yaml", mode="r") as uc7_config:
|
||||
cfg = yaml.safe_load(uc7_config)
|
||||
cfg["io_settings"]["save_sys_logs"] = False
|
||||
cfg["agents"][32]["agent_settings"]["start_step"] = START_STEP
|
||||
cfg["agents"][32]["agent_settings"]["frequency"] = FREQUENCY
|
||||
cfg["agents"][32]["agent_settings"]["variance"] = VARIANCE
|
||||
cfg["agents"][32]["agent_settings"]["repeat_kill_chain"] = kwargs["repeat_kill_chain"]
|
||||
cfg["agents"][32]["agent_settings"]["repeat_kill_chain_stages"] = kwargs["repeat_kill_chain_stages"]
|
||||
cfg["agents"][32]["agent_settings"]["kill_chain"]["MANIPULATION"]["probability"] = kwargs[
|
||||
"manipulation_probability"
|
||||
]
|
||||
cfg["agents"][32]["agent_settings"]["kill_chain"]["ACCESS"]["probability"] = kwargs["access_probability"]
|
||||
cfg["agents"][32]["agent_settings"]["kill_chain"]["PLANNING"]["probability"] = kwargs["planning_probability"]
|
||||
env = PrimaiteGymEnv(env_config=cfg)
|
||||
return env
|
||||
|
||||
|
||||
def test_tap003_repeating_kill_chain():
|
||||
"""Tests to check that TAP003 repeats it's kill chain after success"""
|
||||
env = uc7_tap003_env(
|
||||
repeat_kill_chain=True,
|
||||
repeat_kill_chain_stages=True,
|
||||
manipulation_probability=1,
|
||||
access_probability=1,
|
||||
planning_probability=1,
|
||||
)
|
||||
tap003: TAP003 = env.game.agents["attacker"]
|
||||
for _ in range(40): # This for loop should never actually fully complete.
|
||||
if tap003.current_kill_chain_stage == BaseKillChain.SUCCEEDED:
|
||||
break
|
||||
env.step(0)
|
||||
|
||||
# Catches if the above for loop fully completes.
|
||||
# This test uses a probability of 1 for all stages and a variance of 2 timesteps
|
||||
# Thus the for loop above should never fail.
|
||||
# If this occurs then there is an error somewhere in either:
|
||||
# 1. The TAP Logic
|
||||
# 2. Failing Agent Actions are causing the TAP to fail. (See tap_return_handler).
|
||||
if tap003.current_kill_chain_stage != BaseKillChain.SUCCEEDED:
|
||||
pytest.fail("Attacker Never Reached SUCCEEDED - Please evaluate current TAP Logic.")
|
||||
|
||||
# Stepping twice for the succeeded logic to kick in:
|
||||
env.step(0)
|
||||
env.step(0)
|
||||
|
||||
assert tap003.current_kill_chain_stage.name == InsiderKillChain.RECONNAISSANCE.name
|
||||
assert tap003.next_kill_chain_stage.name == InsiderKillChain.PLANNING.name
|
||||
|
||||
|
||||
def test_tap003_repeating_kill_chain_stages():
|
||||
"""Tests to check that TAP003 repeats it's kill chain after failing a kill chain stage."""
|
||||
env = uc7_tap003_env(
|
||||
repeat_kill_chain=True,
|
||||
repeat_kill_chain_stages=True,
|
||||
manipulation_probability=1,
|
||||
# Probability 0 = Will never be able to perform the access stage and progress to Manipulation.
|
||||
access_probability=0,
|
||||
planning_probability=1,
|
||||
)
|
||||
tap003: TAP003 = env.game.agents["attacker"]
|
||||
env.step(0) # Skipping not started
|
||||
env.step(0) # Successful on the first stage
|
||||
assert tap003.current_kill_chain_stage.name == InsiderKillChain.RECONNAISSANCE.name
|
||||
assert tap003.next_kill_chain_stage.name == InsiderKillChain.PLANNING.name
|
||||
env.step(0) # Successful progression to the second stage
|
||||
env.step(0)
|
||||
assert tap003.current_kill_chain_stage.name == InsiderKillChain.PLANNING.name
|
||||
assert tap003.next_kill_chain_stage.name == InsiderKillChain.ACCESS.name
|
||||
env.step(0) # Successfully moved onto access.
|
||||
env.step(0)
|
||||
assert tap003.current_kill_chain_stage.name == InsiderKillChain.ACCESS.name
|
||||
assert tap003.next_kill_chain_stage.name == InsiderKillChain.MANIPULATION.name
|
||||
env.step(0) # Failure to progress past the third stage.
|
||||
env.step(0)
|
||||
assert tap003.current_kill_chain_stage.name == InsiderKillChain.ACCESS.name
|
||||
assert tap003.next_kill_chain_stage.name == InsiderKillChain.MANIPULATION.name
|
||||
@@ -0,0 +1,232 @@
|
||||
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
from primaite.config.load import _EXAMPLE_CFG
|
||||
from primaite.game.agent.scripted_agents.abstract_tap import (
|
||||
AbstractTAP,
|
||||
BaseKillChain,
|
||||
KillChainOptions,
|
||||
KillChainStageOptions,
|
||||
KillChainStageProgress,
|
||||
)
|
||||
from primaite.game.agent.scripted_agents.TAP001 import MobileMalwareKillChain
|
||||
from primaite.game.agent.scripted_agents.TAP003 import InsiderKillChain
|
||||
from primaite.session.environment import PrimaiteGymEnv
|
||||
from primaite.simulator.network.hardware.nodes.network.router import ACLAction
|
||||
|
||||
# Defining constants.
|
||||
|
||||
START_STEP = 1 # The starting step of the agent.
|
||||
FREQUENCY = 2 # The frequency of kill chain stage progression (E.g it's next attempt at "attacking").
|
||||
VARIANCE = 0 # The timestep variance between kill chain progression (E.g Next timestep = Frequency +/- variance)
|
||||
REPEAT_KILL_CHAIN = False # Should the TAP repeat the kill chain after success/failure?
|
||||
REPEAT_KILL_CHAIN_STAGES = False # Should the TAP restart from it's previous stage on failure?
|
||||
KILL_CHAIN_PROBABILITY = 1 # Blank probability for agent 'success'
|
||||
|
||||
|
||||
def uc7_tap003_env() -> PrimaiteGymEnv:
|
||||
"""Setups the UC7 TAP003 Game with the start_step & frequency set to 1 with probabilities set to 1 as well"""
|
||||
with open(_EXAMPLE_CFG / "uc7_config_tap003.yaml", mode="r") as uc7_config:
|
||||
cfg = yaml.safe_load(uc7_config)
|
||||
cfg["io_settings"]["save_sys_logs"] = False
|
||||
cfg["agents"][32]["agent_settings"]["start_step"] = START_STEP
|
||||
cfg["agents"][32]["agent_settings"]["frequency"] = FREQUENCY
|
||||
cfg["agents"][32]["agent_settings"]["variance"] = VARIANCE
|
||||
cfg["agents"][32]["agent_settings"]["repeat_kill_chain"] = REPEAT_KILL_CHAIN_STAGES
|
||||
cfg["agents"][32]["agent_settings"]["repeat_kill_chain_stages"] = REPEAT_KILL_CHAIN_STAGES
|
||||
cfg["agents"][32]["agent_settings"]["kill_chain"]["MANIPULATION"]["probability"] = KILL_CHAIN_PROBABILITY
|
||||
cfg["agents"][32]["agent_settings"]["kill_chain"]["ACCESS"]["probability"] = KILL_CHAIN_PROBABILITY
|
||||
cfg["agents"][32]["agent_settings"]["kill_chain"]["PLANNING"]["probability"] = KILL_CHAIN_PROBABILITY
|
||||
cfg["agents"][32]["agent_settings"]["kill_chain"]["EXPLOIT"]["probability"] = KILL_CHAIN_PROBABILITY
|
||||
env = PrimaiteGymEnv(env_config=cfg)
|
||||
return env
|
||||
|
||||
|
||||
def environment_step(i: int, env: PrimaiteGymEnv) -> PrimaiteGymEnv:
|
||||
"""Carries out i (given parameter) steps in the environment.."""
|
||||
for x in range(i):
|
||||
env.step(0)
|
||||
return env
|
||||
|
||||
|
||||
def test_tap003_kill_chain_stage_reconnaissance():
|
||||
"""Tests the successful/failed handlers in the reconnaissance stage in the Insider Kill Chain InsiderKillChain"""
|
||||
|
||||
# Instantiating the relevant simulation/game objects:
|
||||
env = uc7_tap003_env()
|
||||
tap003: TAP003 = env.game.agents["attacker"]
|
||||
assert tap003.current_kill_chain_stage == BaseKillChain.NOT_STARTED
|
||||
|
||||
# Frequency is set to two steps
|
||||
env = environment_step(i=2, env=env)
|
||||
|
||||
# Testing that TAP003 Enters into the expected kill chain stages
|
||||
assert tap003.current_kill_chain_stage.name == InsiderKillChain.RECONNAISSANCE.name
|
||||
|
||||
|
||||
def test_tap003_kill_chain_stage_planning():
|
||||
"""Tests the successful/failed handlers in the planning stage in the Insider Kill Chain (TAP003)"""
|
||||
env = uc7_tap003_env()
|
||||
tap003: TAP003 = env.game.agents["attacker"]
|
||||
|
||||
assert tap003.current_kill_chain_stage == BaseKillChain.NOT_STARTED
|
||||
|
||||
env = environment_step(i=2, env=env)
|
||||
|
||||
assert tap003.current_kill_chain_stage.name == InsiderKillChain.RECONNAISSANCE.name
|
||||
assert tap003.next_kill_chain_stage.name == InsiderKillChain.PLANNING.name
|
||||
|
||||
env = environment_step(i=2, env=env)
|
||||
|
||||
# Testing that TAP003 Enters into the expected kill chain stages
|
||||
assert tap003.current_kill_chain_stage.name == InsiderKillChain.PLANNING.name
|
||||
assert tap003.next_kill_chain_stage.name == InsiderKillChain.ACCESS.name
|
||||
|
||||
env = environment_step(i=2, env=env)
|
||||
|
||||
# Testing that the stage successfully impacted the simulation - User is logged in
|
||||
# TODO: Add an assert for this.
|
||||
|
||||
|
||||
def test_tap003_kill_chain_stage_access():
|
||||
"""Tests the successful/failed handlers in the access stage in the InsiderKillChain"""
|
||||
env = uc7_tap003_env()
|
||||
tap003: TAP003 = env.game.agents["attacker"]
|
||||
|
||||
assert tap003.current_kill_chain_stage == BaseKillChain.NOT_STARTED
|
||||
|
||||
env = environment_step(i=2, env=env)
|
||||
|
||||
assert tap003.current_kill_chain_stage.name == InsiderKillChain.RECONNAISSANCE.name
|
||||
assert tap003.next_kill_chain_stage.name == InsiderKillChain.PLANNING.name
|
||||
|
||||
env = environment_step(i=2, env=env)
|
||||
|
||||
assert tap003.current_kill_chain_stage.name == InsiderKillChain.PLANNING.name
|
||||
assert tap003.next_kill_chain_stage.name == InsiderKillChain.ACCESS.name
|
||||
|
||||
env = environment_step(i=2, env=env)
|
||||
|
||||
assert tap003.current_kill_chain_stage.name == InsiderKillChain.ACCESS.name
|
||||
assert tap003.next_kill_chain_stage.name == InsiderKillChain.MANIPULATION.name
|
||||
|
||||
env = environment_step(i=2, env=env)
|
||||
|
||||
|
||||
def test_tap003_kill_chain_stage_manipulation():
|
||||
"""Tests the successful/failed handlers in the manipulation stage in the InsiderKillChain"""
|
||||
|
||||
env = uc7_tap003_env()
|
||||
env.reset()
|
||||
tap003: TAP003 = env.game.agents["attacker"]
|
||||
|
||||
assert tap003.current_kill_chain_stage == BaseKillChain.NOT_STARTED
|
||||
|
||||
env.step(0)
|
||||
env.step(0)
|
||||
|
||||
assert tap003.current_kill_chain_stage.name == InsiderKillChain.RECONNAISSANCE.name
|
||||
assert tap003.next_kill_chain_stage.name == InsiderKillChain.PLANNING.name
|
||||
|
||||
env.step(0)
|
||||
env.step(0)
|
||||
|
||||
assert tap003.current_kill_chain_stage.name == InsiderKillChain.PLANNING.name
|
||||
assert tap003.next_kill_chain_stage.name == InsiderKillChain.ACCESS.name
|
||||
|
||||
env.step(0)
|
||||
env.step(0)
|
||||
|
||||
assert tap003.current_kill_chain_stage.name == InsiderKillChain.ACCESS.name
|
||||
assert tap003.next_kill_chain_stage.name == InsiderKillChain.MANIPULATION.name
|
||||
|
||||
env.step(0)
|
||||
env.step(0)
|
||||
|
||||
assert tap003.current_kill_chain_stage.name == InsiderKillChain.MANIPULATION.name
|
||||
|
||||
# Testing that the stage successfully impacted the simulation - Accounts Altered
|
||||
env.step(0)
|
||||
env.step(0)
|
||||
env.step(0)
|
||||
env.step(0)
|
||||
env.step(0)
|
||||
st_intra_prv_rt_dr_1: Router = env.game.simulation.network.get_node_by_hostname("ST-INTRA-PRV-RT-DR-1")
|
||||
assert st_intra_prv_rt_dr_1.user_manager.admins["admin"].password == "red_pass"
|
||||
|
||||
env.step(0)
|
||||
env.step(0)
|
||||
env.step(0)
|
||||
env.step(0)
|
||||
env.step(0)
|
||||
st_intra_prv_rt_cr: Router = env.game.simulation.network.get_node_by_hostname("ST-INTRA-PRV-RT-CR")
|
||||
assert st_intra_prv_rt_cr.user_manager.admins["admin"].password == "red_pass"
|
||||
|
||||
env.step(0)
|
||||
env.step(0)
|
||||
env.step(0)
|
||||
env.step(0)
|
||||
env.step(0)
|
||||
rem_pub_rt_dr: Router = env.game.simulation.network.get_node_by_hostname("REM-PUB-RT-DR")
|
||||
assert rem_pub_rt_dr.user_manager.admins["admin"].password == "red_pass"
|
||||
|
||||
|
||||
def test_tap003_kill_chain_stage_exploit():
|
||||
"""Tests the successful/failed handlers in the exploit stage in the InsiderKillChain"""
|
||||
|
||||
env = uc7_tap003_env()
|
||||
tap003: TAP003 = env.game.agents["attacker"]
|
||||
# The TAP003's Target Router/Firewall
|
||||
st_intra_prv_rt_dr_1: Router = env.game.simulation.network.get_node_by_hostname("ST-INTRA-PRV-RT-DR-1")
|
||||
st_intra_prv_rt_cr: Router = env.game.simulation.network.get_node_by_hostname("ST-INTRA-PRV-RT-CR")
|
||||
rem_pub_rt_dr: Router = env.game.simulation.network.get_node_by_hostname("REM-PUB-RT-DR")
|
||||
assert tap003.current_kill_chain_stage == BaseKillChain.NOT_STARTED
|
||||
|
||||
env.step(0)
|
||||
env.step(0)
|
||||
|
||||
assert tap003.current_kill_chain_stage.name == InsiderKillChain.RECONNAISSANCE.name
|
||||
assert tap003.next_kill_chain_stage.name == InsiderKillChain.PLANNING.name
|
||||
|
||||
env.step(0)
|
||||
env.step(0)
|
||||
|
||||
assert tap003.current_kill_chain_stage.name == InsiderKillChain.PLANNING.name
|
||||
assert tap003.next_kill_chain_stage.name == InsiderKillChain.ACCESS.name
|
||||
|
||||
env.step(0)
|
||||
env.step(0)
|
||||
|
||||
assert tap003.current_kill_chain_stage.name == InsiderKillChain.ACCESS.name
|
||||
assert tap003.next_kill_chain_stage.name == InsiderKillChain.MANIPULATION.name
|
||||
|
||||
env.step(0)
|
||||
env.step(0)
|
||||
env.step(0)
|
||||
env.step(0)
|
||||
env.step(0)
|
||||
env.step(0)
|
||||
env.step(0)
|
||||
env.step(0)
|
||||
env.step(0)
|
||||
env.step(0)
|
||||
|
||||
assert tap003.current_kill_chain_stage.name == InsiderKillChain.EXPLOIT.name
|
||||
|
||||
# Testing that the stage successfully impacted the simulation - Malicious ACL Added:
|
||||
for _ in range(32):
|
||||
env.step(0)
|
||||
|
||||
# Tests that the ACL has been added and that the action is deny.
|
||||
st_intra_prv_rt_dr_1_acl_list = st_intra_prv_rt_dr_1.acl
|
||||
assert st_intra_prv_rt_dr_1_acl_list.acl[1].action != None
|
||||
assert st_intra_prv_rt_dr_1_acl_list.acl[1].action == ACLAction.DENY
|
||||
|
||||
st_intra_prv_rt_cr_acl_list = st_intra_prv_rt_cr.acl
|
||||
assert st_intra_prv_rt_cr_acl_list.acl[1].action != None
|
||||
assert st_intra_prv_rt_cr_acl_list.acl[1].action == ACLAction.DENY
|
||||
|
||||
rem_pub_rt_dr_acl_list = rem_pub_rt_dr.acl
|
||||
assert rem_pub_rt_dr_acl_list.acl[1].action != None
|
||||
assert rem_pub_rt_dr_acl_list.acl[1].action == ACLAction.DENY
|
||||
@@ -0,0 +1,201 @@
|
||||
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
|
||||
from typing import Protocol
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
from primaite.config.load import _EXAMPLE_CFG
|
||||
from primaite.game.agent.scripted_agents.abstract_tap import (
|
||||
AbstractTAP,
|
||||
BaseKillChain,
|
||||
KillChainOptions,
|
||||
KillChainStageOptions,
|
||||
KillChainStageProgress,
|
||||
)
|
||||
from primaite.game.agent.scripted_agents.TAP001 import MobileMalwareKillChain
|
||||
from primaite.game.agent.scripted_agents.TAP003 import InsiderKillChain
|
||||
from primaite.session.environment import PrimaiteGymEnv
|
||||
from primaite.simulator.network.hardware.nodes.network.router import ACLAction, Router
|
||||
from primaite.utils.validation.ip_protocol import PROTOCOL_LOOKUP
|
||||
from primaite.utils.validation.ipv4_address import IPV4Address
|
||||
from primaite.utils.validation.port import PORT_LOOKUP
|
||||
|
||||
# Defining constants.
|
||||
ATTACK_AGENT_INDEX = 32
|
||||
START_STEP = 1 # The starting step of the agent.
|
||||
FREQUENCY = 2 # The frequency of kill chain stage progression (E.g it's next attempt at "attacking").
|
||||
VARIANCE = 0 # The timestep variance between kill chain progression (E.g Next timestep = Frequency +/- variance)
|
||||
REPEAT_KILL_CHAIN = False # Should the TAP repeat the kill chain after success/failure?
|
||||
REPEAT_KILL_CHAIN_STAGES = False # Should the TAP restart from it's previous stage on failure?
|
||||
KILL_CHAIN_PROBABILITY = 1 # Blank probability for agent 'success'
|
||||
RULES = [
|
||||
{
|
||||
"target_router": "ST-INTRA-PRV-RT-DR-1",
|
||||
"position": 1,
|
||||
"permission": "DENY",
|
||||
"src_ip": "192.168.220.3",
|
||||
"src_wildcard": "NONE",
|
||||
"dst_ip": "192.168.220.3",
|
||||
"dst_wildcard": "NONE",
|
||||
"src_port": "ALL",
|
||||
"dst_port": "ALL",
|
||||
"protocol_name": "ALL",
|
||||
},
|
||||
{
|
||||
"target_router": "ST-INTRA-PRV-RT-DR-2",
|
||||
"position": 5,
|
||||
"permission": "DENY",
|
||||
"src_ip": "192.168.220.3",
|
||||
"src_wildcard": "NONE",
|
||||
"dst_ip": "ALL",
|
||||
"dst_wildcard": "NONE",
|
||||
"src_port": "ALL",
|
||||
"dst_port": "ALL",
|
||||
"protocol_name": "ALL",
|
||||
},
|
||||
{
|
||||
"target_router": "ST-INTRA-PRV-RT-CR",
|
||||
"position": 6,
|
||||
"permission": "PERMIT",
|
||||
"src_ip": "192.168.220.3",
|
||||
"src_wildcard": "NONE",
|
||||
"dst_ip": "ALL",
|
||||
"dst_wildcard": "NONE",
|
||||
"src_port": "ALL",
|
||||
"dst_port": "ALL",
|
||||
"protocol_name": "ALL",
|
||||
},
|
||||
{
|
||||
"target_router": "REM-PUB-RT-DR",
|
||||
"position": 3,
|
||||
"permission": "PERMIT",
|
||||
"src_ip": "192.168.220.3",
|
||||
"src_wildcard": "0.0.0.1",
|
||||
"dst_ip": "192.168.220.3",
|
||||
"dst_wildcard": "0.0.0.1",
|
||||
"src_port": "FTP",
|
||||
"dst_port": "FTP",
|
||||
"protocol_name": "TCP",
|
||||
},
|
||||
#
|
||||
]
|
||||
|
||||
|
||||
def uc7_tap003_env(**kwargs) -> PrimaiteGymEnv:
|
||||
"""Setups the UC7 TAP003 Game with the start_step & frequency set to 1 with probabilities set to 1 as well"""
|
||||
with open(_EXAMPLE_CFG / "uc7_config_tap003.yaml", mode="r") as uc7_config:
|
||||
cfg = yaml.safe_load(uc7_config)
|
||||
cfg["io_settings"]["save_sys_logs"] = False
|
||||
cfg["agents"][ATTACK_AGENT_INDEX]["agent_settings"]["start_step"] = START_STEP
|
||||
cfg["agents"][ATTACK_AGENT_INDEX]["agent_settings"]["frequency"] = FREQUENCY
|
||||
cfg["agents"][ATTACK_AGENT_INDEX]["agent_settings"]["variance"] = VARIANCE
|
||||
cfg["agents"][ATTACK_AGENT_INDEX]["agent_settings"]["repeat_kill_chain"] = kwargs["repeat_kill_chain"]
|
||||
cfg["agents"][ATTACK_AGENT_INDEX]["agent_settings"]["repeat_kill_chain_stages"] = kwargs[
|
||||
"repeat_kill_chain_stages"
|
||||
]
|
||||
cfg["agents"][ATTACK_AGENT_INDEX]["agent_settings"]["kill_chain"]["MANIPULATION"]["probability"] = kwargs[
|
||||
"manipulation_probability"
|
||||
]
|
||||
cfg["agents"][ATTACK_AGENT_INDEX]["agent_settings"]["kill_chain"]["ACCESS"]["probability"] = kwargs[
|
||||
"access_probability"
|
||||
]
|
||||
cfg["agents"][ATTACK_AGENT_INDEX]["agent_settings"]["kill_chain"]["PLANNING"]["probability"] = kwargs[
|
||||
"planning_probability"
|
||||
]
|
||||
cfg["agents"][ATTACK_AGENT_INDEX]["agent_settings"]["kill_chain"]["EXPLOIT"]["malicious_acls"] = RULES
|
||||
# Adding the new test target to TAP003's starting knowledge:
|
||||
new_target_dict = {
|
||||
"ST-INTRA-PRV-RT-DR-2": {
|
||||
"ip_address": "192.168.170.2",
|
||||
"username": "admin",
|
||||
"password": "admin",
|
||||
}
|
||||
}
|
||||
new_target_manipulation = {
|
||||
"host": "ST-INTRA-PRV-RT-DR-2",
|
||||
"ip_address": "192.168.170.2",
|
||||
"action": "change_password",
|
||||
"username": "admin",
|
||||
"new_password": "red_pass",
|
||||
}
|
||||
cfg["agents"][ATTACK_AGENT_INDEX]["agent_settings"]["kill_chain"]["PLANNING"]["starting_network_knowledge"][
|
||||
"credentials"
|
||||
].update(new_target_dict)
|
||||
cfg["agents"][ATTACK_AGENT_INDEX]["agent_settings"]["kill_chain"]["MANIPULATION"]["account_changes"].append(
|
||||
new_target_manipulation
|
||||
)
|
||||
env = PrimaiteGymEnv(env_config=cfg)
|
||||
return env
|
||||
|
||||
|
||||
def test_tap003_cycling_rules():
|
||||
"""Tests to check that TAP003 repeats it's kill chain after success"""
|
||||
|
||||
env = uc7_tap003_env(
|
||||
repeat_kill_chain=True,
|
||||
repeat_kill_chain_stages=True,
|
||||
manipulation_probability=1,
|
||||
access_probability=1,
|
||||
planning_probability=1,
|
||||
)
|
||||
tap003: TAP003 = env.game.agents["attacker"]
|
||||
|
||||
def wait_until_attack():
|
||||
for _ in range(120):
|
||||
# check if the agent has executed and therefore moved onto the next rule index
|
||||
env.step(0)
|
||||
if tap003.history[-1].action == "node-send-remote-command":
|
||||
if tap003.history[-1].parameters["command"][0] == "acl":
|
||||
return
|
||||
pytest.fail("While testing the cycling of TAP003 rules, the agent unexpectedly didn't execute its attack.")
|
||||
|
||||
wait_until_attack()
|
||||
target_node: Router = env.game.simulation.network.get_node_by_hostname("ST-INTRA-PRV-RT-DR-1")
|
||||
assert (rule_0 := target_node.acl.acl[1]) is not None
|
||||
assert rule_0.action == ACLAction.DENY
|
||||
assert rule_0.protocol == None
|
||||
assert rule_0.src_ip_address == IPV4Address("192.168.220.3")
|
||||
assert rule_0.src_wildcard_mask == None
|
||||
assert rule_0.dst_ip_address == IPV4Address("192.168.220.3")
|
||||
assert rule_0.dst_wildcard_mask == None
|
||||
assert rule_0.src_port == None
|
||||
assert rule_0.dst_port == None
|
||||
|
||||
target_node: Router = env.game.simulation.network.get_node_by_hostname("ST-INTRA-PRV-RT-DR-2")
|
||||
wait_until_attack()
|
||||
assert (rule_1 := target_node.acl.acl[5]) is not None
|
||||
assert rule_1.action == ACLAction.DENY
|
||||
assert rule_1.protocol == None
|
||||
assert rule_1.src_ip_address == IPV4Address("192.168.220.3")
|
||||
assert rule_1.src_wildcard_mask == None
|
||||
assert rule_1.dst_ip_address == None
|
||||
assert rule_1.dst_wildcard_mask == None
|
||||
assert rule_1.src_port == None
|
||||
assert rule_1.dst_port == None
|
||||
|
||||
wait_until_attack()
|
||||
target_node: Router = env.game.simulation.network.get_node_by_hostname("ST-INTRA-PRV-RT-CR")
|
||||
assert (rule_2 := target_node.acl.acl[6]) is not None
|
||||
assert rule_2.action == ACLAction.PERMIT
|
||||
assert rule_2.protocol == None
|
||||
assert rule_2.src_ip_address == IPV4Address("192.168.220.3")
|
||||
assert rule_2.src_wildcard_mask == None # default
|
||||
assert rule_2.dst_ip_address == None
|
||||
assert rule_2.dst_wildcard_mask == None # default
|
||||
assert rule_2.src_port == None
|
||||
assert rule_2.dst_port == None
|
||||
|
||||
wait_until_attack()
|
||||
target_node: Router = env.game.simulation.network.get_node_by_hostname("REM-PUB-RT-DR")
|
||||
assert (rule_3 := target_node.acl.acl[3]) is not None
|
||||
assert rule_3.action == ACLAction.PERMIT
|
||||
assert rule_3.protocol == PROTOCOL_LOOKUP["TCP"]
|
||||
assert rule_3.src_ip_address == IPV4Address("192.168.220.3")
|
||||
assert rule_3.src_wildcard_mask == IPV4Address("0.0.0.1")
|
||||
assert rule_3.dst_ip_address == IPV4Address("192.168.220.3")
|
||||
assert rule_3.dst_wildcard_mask == IPV4Address("0.0.0.1")
|
||||
assert rule_3.src_port == PORT_LOOKUP["FTP"]
|
||||
assert rule_3.dst_port == PORT_LOOKUP["FTP"]
|
||||
|
||||
# If we've gotten this fair then we can pass the test :)
|
||||
pass
|
||||
Reference in New Issue
Block a user