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:
Marek Wolan
2025-02-10 14:39:28 +00:00
parent 0d1edf0362
commit d8c8aa40a4
118 changed files with 21789 additions and 368 deletions

View 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")

View 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)

View File

@@ -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)

View File

@@ -0,0 +1 @@
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK

View File

@@ -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

View File

@@ -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__)

View File

@@ -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

View File

@@ -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")

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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