Skip to content

Commit 41f391f

Browse files
authored
Merge pull request #397 from Limmen/emulation
emulation_util unit test
2 parents 2b1b2cf + bae1e0a commit 41f391f

File tree

1 file changed

+328
-0
lines changed

1 file changed

+328
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,328 @@
1+
from unittest.mock import patch, MagicMock
2+
from csle_common.util.emulation_util import EmulationUtil
3+
from csle_common.dao.emulation_observation.common.emulation_connection_observation_state import (
4+
EmulationConnectionObservationState,
5+
)
6+
from csle_common.dao.emulation_action.attacker.emulation_attacker_action_type import EmulationAttackerActionType
7+
8+
9+
class TestEmulationUtilSuite:
10+
"""
11+
Test suite for emulation_util
12+
"""
13+
14+
@patch("csle_common.util.ssh_util.SSHUtil.execute_ssh_cmds")
15+
def test_execute_ssh_cmds(self, mock_execute_ssh_cmds) -> None:
16+
"""
17+
Test the method that executes a list of commands over an ssh connection to the emulation
18+
19+
:param mock_execute_ssh_cmds: mock_execute_ssh_cmds
20+
21+
:return: None
22+
"""
23+
cmds = ["ls", "pwd", "whoami"]
24+
mock_conn = MagicMock()
25+
wait_for_completion = True
26+
27+
mock_return_value = [(b"file1\nfile2\n", b"", 0.1), (b"/home/user\n", b"", 0.05), (b"user\n", b"", 0.02)]
28+
mock_execute_ssh_cmds.return_value = mock_return_value
29+
result = EmulationUtil.execute_ssh_cmds(cmds=cmds, conn=mock_conn, wait_for_completion=wait_for_completion)
30+
mock_execute_ssh_cmds.assert_called()
31+
assert result == mock_return_value
32+
33+
@patch("csle_common.util.ssh_util.SSHUtil.execute_ssh_cmd")
34+
def test_execute_ssh_cmd(self, mock_execute_ssh_cmd) -> None:
35+
"""
36+
Test the method that executes an action on the emulation over a ssh connection
37+
38+
:param mock_execute_ssh_cmd: mock_execute_ssh_cmd
39+
40+
:return: None
41+
"""
42+
cmd = "ls -l"
43+
mock_conn = MagicMock()
44+
wait_for_completion = True
45+
46+
mock_return_value = (b"file1\nfile2\n", b"", 0.1)
47+
mock_execute_ssh_cmd.return_value = mock_return_value
48+
result = EmulationUtil.execute_ssh_cmd(cmd=cmd, conn=mock_conn, wait_for_completion=wait_for_completion)
49+
mock_execute_ssh_cmd.assert_called()
50+
assert result == mock_return_value
51+
52+
def test_log_measured_action_time(self) -> None:
53+
"""
54+
Test the method that logs the measured time of an action to Kafka
55+
56+
:return: None
57+
"""
58+
total_time = 6
59+
action = MagicMock()
60+
emulation_env_config = MagicMock()
61+
create_producer = True
62+
action.to_kafka_record.return_value = "mock_record"
63+
EmulationUtil.log_measured_action_time(
64+
total_time=total_time,
65+
action=action,
66+
emulation_env_config=emulation_env_config,
67+
create_producer=create_producer,
68+
)
69+
action.to_kafka_record.assert_called()
70+
71+
@patch("csle_common.util.ssh_util.SSHUtil.execute_ssh_cmd")
72+
def test_check_if_ssh_server_is_running_ssh(self, mock_execute_ssh_cmd) -> None:
73+
"""
74+
Test the method that checks if an ssh server is running on the machine
75+
76+
:param mock_execute_ssh_cmd: mock_execute_ssh_cmd
77+
78+
:return: None
79+
"""
80+
mock_conn = MagicMock()
81+
mock_execute_ssh_cmd.return_value = (b"sshd is running", b"", 0.1)
82+
result = EmulationUtil._check_if_ssh_server_is_running(conn=mock_conn, telnet=False)
83+
mock_execute_ssh_cmd.assert_called()
84+
assert result
85+
86+
@patch("csle_common.util.ssh_util.SSHUtil.execute_ssh_cmd")
87+
def test_list_all_users_ssh(self, mock_execute_ssh_cmd) -> None:
88+
"""
89+
Test the method that list all users on a machine
90+
91+
:param mock_execute_ssh_cmd: mock_execute_ssh_cmd
92+
93+
:return: None
94+
"""
95+
mock_conn = MagicMock()
96+
emulation_env_config = MagicMock()
97+
c = EmulationConnectionObservationState(
98+
conn=mock_conn, credential=MagicMock(), root=False, service="ssh", port=22, ip="192.168.1.2"
99+
)
100+
mock_execute_ssh_cmd.return_value = (b"root\nuser1\nuser2\n", b"", 0.1)
101+
result = EmulationUtil._list_all_users(c=c, emulation_env_config=emulation_env_config, telnet=False)
102+
mock_execute_ssh_cmd.assert_called()
103+
assert result == ["root", "user1", "user2"]
104+
105+
def test_is_connection_active(self) -> None:
106+
"""
107+
Test the method that checks if a given connection is active or not
108+
109+
:return: None
110+
"""
111+
mock_conn = MagicMock()
112+
mock_transport = MagicMock()
113+
mock_transport.is_active.return_value = True
114+
mock_conn.get_transport.return_value = mock_transport
115+
116+
result = EmulationUtil.is_connection_active(conn=mock_conn)
117+
assert result
118+
mock_conn.get_transport.assert_called()
119+
mock_transport.is_active.assert_called()
120+
121+
@patch("paramiko.SSHClient")
122+
def test_setup_custom_connection(self, mock_SSHClient) -> None:
123+
"""
124+
Test utility function for setting up a custom SSH connection given credentials and a proxy connection
125+
126+
:param mock_SSHClient: mock_SSHClient
127+
128+
:return: None
129+
"""
130+
user = "user"
131+
pw = "password"
132+
source_ip = "192.168.1.1"
133+
port = 22
134+
target_ip = "192.168.1.2"
135+
root = True
136+
137+
mock_proxy_conn = MagicMock()
138+
mock_transport = MagicMock()
139+
mock_proxy_conn.conn.get_transport.return_value = mock_transport
140+
141+
mock_ssh_client = MagicMock()
142+
mock_SSHClient.return_value = mock_ssh_client
143+
mock_relay_channel = MagicMock()
144+
mock_transport.open_channel.return_value = mock_relay_channel
145+
mock_transport.is_active.return_value = True
146+
147+
result = EmulationUtil.setup_custom_connection(
148+
user=user, pw=pw, source_ip=source_ip, port=port, target_ip=target_ip, proxy_conn=mock_proxy_conn, root=root
149+
)
150+
mock_SSHClient.assert_called()
151+
assert result
152+
153+
def test_write_remote_file(self) -> None:
154+
"""
155+
Test utility function for writing contents to a file
156+
157+
:return: None
158+
"""
159+
mock_conn = MagicMock()
160+
mock_sftp_client = MagicMock()
161+
mock_remote_file = MagicMock()
162+
163+
mock_conn.open_sftp.return_value = mock_sftp_client
164+
mock_sftp_client.file.return_value = mock_remote_file
165+
166+
file_name = "filename.txt"
167+
contents = "content"
168+
write_mode = "w"
169+
170+
EmulationUtil.write_remote_file(conn=mock_conn, file_name=file_name, contents=contents, write_mode=write_mode)
171+
mock_conn.open_sftp.assert_called()
172+
mock_sftp_client.file.assert_called()
173+
mock_remote_file.write.assert_called()
174+
mock_remote_file.close.assert_called()
175+
176+
def test_connect_admin(self) -> None:
177+
"""
178+
Test the method that connects the admin agent
179+
180+
:return: None
181+
"""
182+
emulation_env_config = MagicMock()
183+
ip = "192.168.1.100"
184+
create_producer = True
185+
EmulationUtil.connect_admin(emulation_env_config=emulation_env_config, ip=ip, create_producer=create_producer)
186+
assert emulation_env_config.agent_ip == ip
187+
emulation_env_config.connect.assert_called()
188+
189+
def test_disconnect_admin(self) -> None:
190+
"""
191+
Test the method that disconnects the admin agent
192+
193+
:return: None
194+
"""
195+
emulation_env_config = MagicMock()
196+
EmulationUtil.disconnect_admin(emulation_env_config=emulation_env_config)
197+
emulation_env_config.close_all_connections.assert_called()
198+
199+
def test_execute_cmd_interactive_channel(self) -> None:
200+
"""
201+
Test method that executes an action on the emulation using an interactive shell (non synchronous)
202+
203+
:return: None
204+
"""
205+
mock_channel = MagicMock()
206+
cmd = "ls -la"
207+
EmulationUtil.execute_cmd_interactive_channel(cmd=cmd, channel=mock_channel)
208+
mock_channel.send.assert_called()
209+
210+
@patch("csle_common.util.emulation_util.EmulationUtil.read_result_interactive_channel")
211+
def test_read_result_interactive(self, mock_read_result_interactive_channel) -> None:
212+
"""
213+
Test the method that reads the result of an action executed in interactive mode
214+
215+
:param mock_read_result_interactive_channel: mock_read_result_interactive_channel
216+
217+
:return: None
218+
"""
219+
mock_read_result_interactive_channel.return_value = "result"
220+
emulation_env_config = MagicMock()
221+
mock_channel = MagicMock()
222+
result = EmulationUtil.read_result_interactive(emulation_env_config=emulation_env_config, channel=mock_channel)
223+
assert result == "result"
224+
225+
def test_read_result_interactive_channel(self) -> None:
226+
"""
227+
Test the method that reads the result of an action executed in interactive mode
228+
229+
:return: None
230+
"""
231+
emulation_env_config = MagicMock()
232+
mock_channel = MagicMock()
233+
mock_channel.recv_ready.side_effect = [False, True]
234+
mock_channel.recv.return_value = b"Mocked result"
235+
mock_shell_escape = MagicMock()
236+
mock_shell_escape.sub.return_value = "result"
237+
result = EmulationUtil.read_result_interactive_channel(
238+
emulation_env_config=emulation_env_config, channel=mock_channel
239+
)
240+
mock_channel.recv_ready.assert_called()
241+
mock_channel.recv.assert_called()
242+
assert result
243+
244+
def test_is_emulation_defense_action_legal(self) -> None:
245+
"""
246+
Test the method that checks if a given defense action is legal in the current state of the environment
247+
248+
:return: None
249+
"""
250+
defense_action_id = 1
251+
emulation_env_config = MagicMock()
252+
emulation_env_state = MagicMock()
253+
result = EmulationUtil.is_emulation_defense_action_legal(
254+
defense_action_id=defense_action_id, env_config=emulation_env_config, env_state=emulation_env_state
255+
)
256+
assert result
257+
258+
@patch("csle_common.util.env_dynamics_util.EnvDynamicsUtil.logged_in_ips_str")
259+
def test_is_emulation_attack_action_legal(self, mock_logged_in_ips_str) -> None:
260+
"""
261+
Test the method that checks if a given attack action is legal in the current state of the environment
262+
263+
:param mock_logged_in_ips_str: mock_logged_in_ips_str
264+
265+
:return: None
266+
"""
267+
mock_env_config = MagicMock()
268+
mock_env_state = MagicMock()
269+
270+
mock_env_state.attacker_action_config.actions = [MagicMock(id=0), MagicMock(id=1)]
271+
mock_env_state.attacker_obs_state = MagicMock()
272+
mock_env_state.attacker_obs_state.get_action_ips.return_value = "192.168.1.1"
273+
mock_env_state.attacker_obs_state.actions_tried = set()
274+
mock_env_state.attacker_obs_state.machines = [
275+
MagicMock(
276+
ips="192.168.1.1",
277+
logged_in=True,
278+
root=False,
279+
filesystem_searched=False,
280+
tools_installed=False,
281+
backdoor_installed=False,
282+
untried_credentials=True,
283+
)
284+
]
285+
286+
action_id = 1
287+
mock_action = MagicMock()
288+
mock_action.id = action_id
289+
mock_action.index = 0
290+
mock_action.type = EmulationAttackerActionType.RECON
291+
mock_env_state.attacker_action_config.actions[action_id] = mock_action
292+
293+
result = EmulationUtil.is_emulation_attack_action_legal(action_id, mock_env_config, mock_env_state)
294+
assert not result
295+
296+
def test_check_pid(self) -> None:
297+
"""
298+
Test the method that checks if a given pid is running on the host
299+
300+
:return: None
301+
"""
302+
result = EmulationUtil.check_pid(1)
303+
assert result
304+
305+
def test_physical_ip_match(self) -> None:
306+
"""
307+
Test utility method for checking if a container ip matches a physical ip
308+
309+
:return: None
310+
"""
311+
emulation_env_config = MagicMock()
312+
emulation_env_config.kafka_config.container.docker_gw_bridge_ip = "192.168.0.1"
313+
emulation_env_config.kafka_config.container.physical_host_ip = "10.0.0.1"
314+
emulation_env_config.elk_config.container.docker_gw_bridge_ip = "192.168.0.2"
315+
emulation_env_config.elk_config.container.physical_host_ip = "10.0.0.2"
316+
emulation_env_config.sdn_controller_config = None
317+
318+
mock_container = MagicMock()
319+
mock_container.physical_host_ip = "10.0.0.3"
320+
emulation_env_config.containers_config.get_container_from_ip.return_value = mock_container
321+
322+
ip = "192.168.0.1"
323+
physical_host_ip = "10.0.0.1"
324+
325+
result = EmulationUtil.physical_ip_match(
326+
emulation_env_config=emulation_env_config, ip=ip, physical_host_ip=physical_host_ip
327+
)
328+
assert result

0 commit comments

Comments
 (0)