diff --git a/.gitignore b/.gitignore index 60f5f54c..ff86b65f 100644 --- a/.gitignore +++ b/.gitignore @@ -150,3 +150,4 @@ src/primaite/outputs/ # benchmark session outputs benchmark/output +src/primaite/notebooks/scratch.ipynb diff --git a/src/primaite/notebooks/create-simulation.ipynb b/src/primaite/notebooks/create-simulation.ipynb index b0a140a1..baf7bd2c 100644 --- a/src/primaite/notebooks/create-simulation.ipynb +++ b/src/primaite/notebooks/create-simulation.ipynb @@ -42,11 +42,11 @@ { "data": { "text/plain": [ - "{'uuid': '5304ed6d-de4c-408c-ae24-ada32852d196',\n", - " 'network': {'uuid': 'fa17dfe8-81a1-4c7f-8c5b-8c2d3b1e8756',\n", + "{'uuid': '2ef348c6-32e5-4c5c-83b7-3b82d0b6123b',\n", + " 'network': {'uuid': 'dd2d1a02-d461-4505-8bbd-fd0681750175',\n", " 'nodes': {},\n", " 'links': {}},\n", - " 'domain': {'uuid': '320cbb83-eb1b-4911-a4f0-fc46d8038a8a', 'accounts': {}}}" + " 'domain': {'uuid': 'ae0423ee-51fa-41e7-be80-c642b39707f6', 'accounts': {}}}" ] }, "execution_count": 2, @@ -56,6 +56,7 @@ ], "source": [ "my_sim = Simulation()\n", + "net = my_sim.network\n", "my_sim.describe_state()" ] }, @@ -82,12 +83,9 @@ "outputs": [], "source": [ "my_pc = Node(hostname=\"primaite_pc\",)\n", + "net.add_node(my_pc)\n", "my_server = Node(hostname=\"google_server\")\n", - "\n", - "# TODO: when there is a proper function for adding nodes, use it instead of manually adding.\n", - "\n", - "my_sim.network.nodes[my_pc.uuid] = my_pc\n", - "my_sim.network.nodes[my_server.uuid] = my_server\n" + "net.add_node(my_server)\n" ] }, { @@ -115,15 +113,16 @@ "name": "stderr", "output_type": "stream", "text": [ - "2023-08-20 18:42:51,310: NIC 5c:b6:26:c0:86:61/130.1.1.1 connected to Link 5c:b6:26:c0:86:61/130.1.1.1<-->01:ef:b1:a3:24:72\n", - "2023-08-20 18:42:51,311: SwitchPort 01:ef:b1:a3:24:72 connected to Link 5c:b6:26:c0:86:61/130.1.1.1<-->01:ef:b1:a3:24:72\n", - "2023-08-20 18:42:51,314: NIC f6:de:1e:63:8e:7f/130.1.1.2 connected to Link f6:de:1e:63:8e:7f/130.1.1.2<-->30:9e:c8:d4:5d:f3\n", - "2023-08-20 18:42:51,315: SwitchPort 30:9e:c8:d4:5d:f3 connected to Link f6:de:1e:63:8e:7f/130.1.1.2<-->30:9e:c8:d4:5d:f3\n" + "2023-08-24 13:06:28,617: NIC cc:be:ec:43:a6:4c/130.1.1.1 connected to Link cc:be:ec:43:a6:4c/130.1.1.1<-->79:2b:4a:70:c3:50\n", + "2023-08-24 13:06:28,618: SwitchPort 79:2b:4a:70:c3:50 connected to Link cc:be:ec:43:a6:4c/130.1.1.1<-->79:2b:4a:70:c3:50\n", + "2023-08-24 13:06:28,619: NIC c2:1e:48:e1:a4:ad/130.1.1.2 connected to Link c2:1e:48:e1:a4:ad/130.1.1.2<-->1a:2d:12:38:80:2f\n", + "2023-08-24 13:06:28,620: SwitchPort 1a:2d:12:38:80:2f connected to Link c2:1e:48:e1:a4:ad/130.1.1.2<-->1a:2d:12:38:80:2f\n" ] } ], "source": [ "my_swtich = Switch(hostname=\"switch1\", num_ports=12)\n", + "net.add_node(my_swtich)\n", "\n", "pc_nic = NIC(ip_address=\"130.1.1.1\", gateway=\"130.1.1.255\", subnet_mask=\"255.255.255.0\")\n", "my_pc.connect_nic(pc_nic)\n", @@ -133,11 +132,8 @@ "my_server.connect_nic(server_nic)\n", "\n", "\n", - "pc_to_switch = Link(endpoint_a=pc_nic, endpoint_b=my_swtich.switch_ports[1])\n", - "server_to_swtich = Link(endpoint_a=server_nic, endpoint_b=my_swtich.switch_ports[2])\n", - "\n", - "my_sim.network.links[pc_to_switch.uuid] = pc_to_switch\n", - "my_sim.network.links[server_to_swtich.uuid] = server_to_swtich" + "net.connect(pc_nic, my_swtich.switch_ports[1])\n", + "net.connect(server_nic, my_swtich.switch_ports[2])\n" ] }, { @@ -175,7 +171,7 @@ { "data": { "text/plain": [ - "FileSystemFile(uuid='253e4606-0f6d-4e57-8db0-6fa7e331ecea', name='favicon.ico', size=40.0, file_type=, action_manager=None)" + "FileSystemFile(uuid='7d56a563-ecc0-4011-8c97-240dd6c885c0', name='favicon.ico', size=40.0, file_type=, action_manager=None)" ] }, "execution_count": 9, @@ -270,31 +266,31 @@ { "data": { "text/plain": [ - "{'uuid': '5304ed6d-de4c-408c-ae24-ada32852d196',\n", - " 'network': {'uuid': 'fa17dfe8-81a1-4c7f-8c5b-8c2d3b1e8756',\n", - " 'nodes': {'1fa46446-6681-4e25-a3ba-c4c2cc564630': {'uuid': '1fa46446-6681-4e25-a3ba-c4c2cc564630',\n", + "{'uuid': '2ef348c6-32e5-4c5c-83b7-3b82d0b6123b',\n", + " 'network': {'uuid': 'dd2d1a02-d461-4505-8bbd-fd0681750175',\n", + " 'nodes': {'2f03b32b-7290-4921-8670-faebe4a19d63': {'uuid': '2f03b32b-7290-4921-8670-faebe4a19d63',\n", " 'hostname': 'primaite_pc',\n", " 'operating_state': 0,\n", - " 'NICs': {'09ca02eb-7733-492c-9eff-f0d6b6ebeeda': {'uuid': '09ca02eb-7733-492c-9eff-f0d6b6ebeeda',\n", + " 'NICs': {'e07e2a7f-b09f-4bd8-8e92-cffbf1f2270b': {'uuid': 'e07e2a7f-b09f-4bd8-8e92-cffbf1f2270b',\n", " 'ip_adress': '130.1.1.1',\n", " 'subnet_mask': '255.255.255.0',\n", " 'gateway': '130.1.1.255',\n", - " 'mac_address': '5c:b6:26:c0:86:61',\n", + " 'mac_address': 'cc:be:ec:43:a6:4c',\n", " 'speed': 100,\n", " 'mtu': 1500,\n", " 'wake_on_lan': False,\n", " 'dns_servers': [],\n", " 'enabled': False}},\n", - " 'file_system': {'uuid': '8b533e31-04e9-4838-839d-0656ace3e57a',\n", - " 'folders': {'b450c223-872c-4fe0-90cc-9da80973eaad': {'uuid': 'b450c223-872c-4fe0-90cc-9da80973eaad',\n", + " 'file_system': {'uuid': '0b7206af-3e0a-41b0-8115-ae9e0dbbcd81',\n", + " 'folders': {'c161bc7c-9abd-4666-9b49-2745fdb65ebe': {'uuid': 'c161bc7c-9abd-4666-9b49-2745fdb65ebe',\n", " 'name': 'downloads',\n", " 'size': 1000.0,\n", - " 'files': {'8160e685-a76f-4171-8a12-3d6b32a9ea16': {'uuid': '8160e685-a76f-4171-8a12-3d6b32a9ea16',\n", + " 'files': {'f807d777-d167-4f37-9f9b-ced634af6ed5': {'uuid': 'f807d777-d167-4f37-9f9b-ced634af6ed5',\n", " 'name': 'firefox_installer.zip',\n", " 'size': 1000.0,\n", " 'file_type': 'ZIP'}},\n", " 'is_quarantined': False}}},\n", - " 'applications': {'c82f1064-f35e-466b-88ae-3f61ba0e5161': {'uuid': 'c82f1064-f35e-466b-88ae-3f61ba0e5161',\n", + " 'applications': {'ea466b2f-1ed5-49fd-9579-44852bff684d': {'uuid': 'ea466b2f-1ed5-49fd-9579-44852bff684d',\n", " 'health_state': 'GOOD',\n", " 'health_state_red_view': 'GOOD',\n", " 'criticality': 'MEDIUM',\n", @@ -312,48 +308,111 @@ " 'groups': []}},\n", " 'services': {},\n", " 'process': {}},\n", - " '7f637689-6f91-4026-a685-48a9067f03e8': {'uuid': '7f637689-6f91-4026-a685-48a9067f03e8',\n", + " 'e9afc0bc-fb21-48a3-9868-2ede6a3181dc': {'uuid': 'e9afc0bc-fb21-48a3-9868-2ede6a3181dc',\n", " 'hostname': 'google_server',\n", " 'operating_state': 0,\n", - " 'NICs': {'1abc7272-c516-4463-bd07-1a3cefe39313': {'uuid': '1abc7272-c516-4463-bd07-1a3cefe39313',\n", + " 'NICs': {'956ce240-8fb3-4fde-8635-ac4ea601a582': {'uuid': '956ce240-8fb3-4fde-8635-ac4ea601a582',\n", " 'ip_adress': '130.1.1.2',\n", " 'subnet_mask': '255.255.255.0',\n", " 'gateway': '130.1.1.255',\n", - " 'mac_address': 'f6:de:1e:63:8e:7f',\n", + " 'mac_address': 'c2:1e:48:e1:a4:ad',\n", " 'speed': 100,\n", " 'mtu': 1500,\n", " 'wake_on_lan': False,\n", " 'dns_servers': [],\n", " 'enabled': False}},\n", - " 'file_system': {'uuid': 'ac9a6643-8349-4f7a-98c7-a1a9f97ce123',\n", - " 'folders': {'befa5d92-0878-4da2-9dac-f993c0b4a554': {'uuid': 'befa5d92-0878-4da2-9dac-f993c0b4a554',\n", + " 'file_system': {'uuid': 'c3f99c30-b493-4fb6-b13e-d2005d851b59',\n", + " 'folders': {'869eda49-21f2-4fc1-8681-78725cdd5c70': {'uuid': '869eda49-21f2-4fc1-8681-78725cdd5c70',\n", " 'name': 'static',\n", " 'size': 0,\n", " 'files': {},\n", " 'is_quarantined': False},\n", - " '27383b5e-8884-4ec0-bb50-a5d43e460dfa': {'uuid': '27383b5e-8884-4ec0-bb50-a5d43e460dfa',\n", + " '9fbe0e41-0d6a-4142-9c73-9c0de2dbde6e': {'uuid': '9fbe0e41-0d6a-4142-9c73-9c0de2dbde6e',\n", " 'name': 'root',\n", " 'size': 40.0,\n", - " 'files': {'253e4606-0f6d-4e57-8db0-6fa7e331ecea': {'uuid': '253e4606-0f6d-4e57-8db0-6fa7e331ecea',\n", + " 'files': {'7d56a563-ecc0-4011-8c97-240dd6c885c0': {'uuid': '7d56a563-ecc0-4011-8c97-240dd6c885c0',\n", " 'name': 'favicon.ico',\n", " 'size': 40.0,\n", " 'file_type': 'PNG'}},\n", " 'is_quarantined': False}}},\n", " 'applications': {},\n", " 'services': {},\n", - " 'process': {}}},\n", - " 'links': {'a449b1ff-50d9-4342-861e-44f2d4dfef37': {'uuid': 'a449b1ff-50d9-4342-861e-44f2d4dfef37',\n", - " 'endpoint_a': '09ca02eb-7733-492c-9eff-f0d6b6ebeeda',\n", - " 'endpoint_b': 'ee4557d9-a309-45dd-a6e0-5b572cc70ee5',\n", + " 'process': {}},\n", + " '47814452-ef47-4e6b-9087-796c438d4698': {'uuid': '47814452-ef47-4e6b-9087-796c438d4698',\n", + " 'num_ports': 12,\n", + " 'ports': {1: {'uuid': 'b76fe86f-bb92-4346-8e83-217a2fb0bc67',\n", + " 'mac_address': '79:2b:4a:70:c3:50',\n", + " 'speed': 100,\n", + " 'mtu': 1500,\n", + " 'enabled': False},\n", + " 2: {'uuid': '6f8fc6e7-76a4-441a-b7af-441edbdcc6ac',\n", + " 'mac_address': '1a:2d:12:38:80:2f',\n", + " 'speed': 100,\n", + " 'mtu': 1500,\n", + " 'enabled': False},\n", + " 3: {'uuid': '1aa75a3c-01f1-4293-9894-5396fa412690',\n", + " 'mac_address': 'd1:7b:36:c1:82:c1',\n", + " 'speed': 100,\n", + " 'mtu': 1500,\n", + " 'enabled': False},\n", + " 4: {'uuid': 'fe6c9f44-59d5-403e-973a-6f19fce7b9b9',\n", + " 'mac_address': 'e3:6b:cc:0c:98:9b',\n", + " 'speed': 100,\n", + " 'mtu': 1500,\n", + " 'enabled': False},\n", + " 5: {'uuid': 'e9e83e37-8537-4884-98a6-87017540078f',\n", + " 'mac_address': '32:09:c0:4a:f1:20',\n", + " 'speed': 100,\n", + " 'mtu': 1500,\n", + " 'enabled': False},\n", + " 6: {'uuid': '747f2cd3-8902-4da8-8829-b0b53fe79735',\n", + " 'mac_address': 'e8:20:0b:04:b8:76',\n", + " 'speed': 100,\n", + " 'mtu': 1500,\n", + " 'enabled': False},\n", + " 7: {'uuid': '88ed129e-0ddb-4d29-ba3c-58d81efe240e',\n", + " 'mac_address': '7f:b4:f4:2e:b6:71',\n", + " 'speed': 100,\n", + " 'mtu': 1500,\n", + " 'enabled': False},\n", + " 8: {'uuid': '6c1a4c3c-25d8-46f6-98a8-54073d0ca0d3',\n", + " 'mac_address': 'f6:22:2d:24:b9:71',\n", + " 'speed': 100,\n", + " 'mtu': 1500,\n", + " 'enabled': False},\n", + " 9: {'uuid': 'b2bfc006-6a6b-4701-a75a-27954592d429',\n", + " 'mac_address': 'b6:a5:92:a5:aa:1b',\n", + " 'speed': 100,\n", + " 'mtu': 1500,\n", + " 'enabled': False},\n", + " 10: {'uuid': '3c607386-87a2-4d0b-ac04-449416ca5b1f',\n", + " 'mac_address': 'b3:75:7d:ce:88:0a',\n", + " 'speed': 100,\n", + " 'mtu': 1500,\n", + " 'enabled': False},\n", + " 11: {'uuid': '590002c8-27fa-4c31-b17b-7b89dbf8cdf8',\n", + " 'mac_address': 'c0:25:a6:64:52:8e',\n", + " 'speed': 100,\n", + " 'mtu': 1500,\n", + " 'enabled': False},\n", + " 12: {'uuid': 'b7e25eed-547a-4c17-8cb9-8b976ce4bbd9',\n", + " 'mac_address': '98:50:96:47:ca:bc',\n", + " 'speed': 100,\n", + " 'mtu': 1500,\n", + " 'enabled': False}},\n", + " 'mac_address_table': {}}},\n", + " 'links': {'a51a4435-20ae-43cf-a151-26e824968b3d': {'uuid': 'a51a4435-20ae-43cf-a151-26e824968b3d',\n", + " 'endpoint_a': 'e07e2a7f-b09f-4bd8-8e92-cffbf1f2270b',\n", + " 'endpoint_b': 'b76fe86f-bb92-4346-8e83-217a2fb0bc67',\n", " 'bandwidth': 100.0,\n", " 'current_load': 0.0},\n", - " 'ebd7687b-ec69-4f1b-b2ba-86669aa95723': {'uuid': 'ebd7687b-ec69-4f1b-b2ba-86669aa95723',\n", - " 'endpoint_a': '1abc7272-c516-4463-bd07-1a3cefe39313',\n", - " 'endpoint_b': 'dc26b764-a07e-486a-99a4-798c8e0c187a',\n", + " 'ae3486e5-f78e-4092-96d1-d7e8176f2b7d': {'uuid': 'ae3486e5-f78e-4092-96d1-d7e8176f2b7d',\n", + " 'endpoint_a': '956ce240-8fb3-4fde-8635-ac4ea601a582',\n", + " 'endpoint_b': '6f8fc6e7-76a4-441a-b7af-441edbdcc6ac',\n", " 'bandwidth': 100.0,\n", " 'current_load': 0.0}}},\n", - " 'domain': {'uuid': '320cbb83-eb1b-4911-a4f0-fc46d8038a8a',\n", - " 'accounts': {'5fdcfb66-84f3-4f0f-a3a7-d0cb0e1a5d51': {'uuid': '5fdcfb66-84f3-4f0f-a3a7-d0cb0e1a5d51',\n", + " 'domain': {'uuid': 'ae0423ee-51fa-41e7-be80-c642b39707f6',\n", + " 'accounts': {'917eda28-9a67-4449-bddd-87e2141a3162': {'uuid': '917eda28-9a67-4449-bddd-87e2141a3162',\n", " 'num_logons': 0,\n", " 'num_logoffs': 0,\n", " 'num_group_changes': 0,\n", @@ -380,7 +439,7 @@ { "data": { "text/plain": [ - "'{\"uuid\": \"5304ed6d-de4c-408c-ae24-ada32852d196\", \"network\": {\"uuid\": \"fa17dfe8-81a1-4c7f-8c5b-8c2d3b1e8756\", \"nodes\": {\"1fa46446-6681-4e25-a3ba-c4c2cc564630\": {\"uuid\": \"1fa46446-6681-4e25-a3ba-c4c2cc564630\", \"hostname\": \"primaite_pc\", \"operating_state\": 0, \"NICs\": {\"09ca02eb-7733-492c-9eff-f0d6b6ebeeda\": {\"uuid\": \"09ca02eb-7733-492c-9eff-f0d6b6ebeeda\", \"ip_adress\": \"130.1.1.1\", \"subnet_mask\": \"255.255.255.0\", \"gateway\": \"130.1.1.255\", \"mac_address\": \"5c:b6:26:c0:86:61\", \"speed\": 100, \"mtu\": 1500, \"wake_on_lan\": false, \"dns_servers\": [], \"enabled\": false}}, \"file_system\": {\"uuid\": \"8b533e31-04e9-4838-839d-0656ace3e57a\", \"folders\": {\"b450c223-872c-4fe0-90cc-9da80973eaad\": {\"uuid\": \"b450c223-872c-4fe0-90cc-9da80973eaad\", \"name\": \"downloads\", \"size\": 1000.0, \"files\": {\"8160e685-a76f-4171-8a12-3d6b32a9ea16\": {\"uuid\": \"8160e685-a76f-4171-8a12-3d6b32a9ea16\", \"name\": \"firefox_installer.zip\", \"size\": 1000.0, \"file_type\": \"ZIP\"}}, \"is_quarantined\": false}}}, \"applications\": {\"c82f1064-f35e-466b-88ae-3f61ba0e5161\": {\"uuid\": \"c82f1064-f35e-466b-88ae-3f61ba0e5161\", \"health_state\": \"GOOD\", \"health_state_red_view\": \"GOOD\", \"criticality\": \"MEDIUM\", \"patching_count\": 0, \"scanning_count\": 0, \"revealed_to_red\": false, \"installing_count\": 0, \"max_sessions\": 1, \"tcp\": true, \"udp\": true, \"ports\": [\"HTTP\"], \"opearting_state\": \"RUNNING\", \"execution_control_status\": \"manual\", \"num_executions\": 0, \"groups\": []}}, \"services\": {}, \"process\": {}}, \"7f637689-6f91-4026-a685-48a9067f03e8\": {\"uuid\": \"7f637689-6f91-4026-a685-48a9067f03e8\", \"hostname\": \"google_server\", \"operating_state\": 0, \"NICs\": {\"1abc7272-c516-4463-bd07-1a3cefe39313\": {\"uuid\": \"1abc7272-c516-4463-bd07-1a3cefe39313\", \"ip_adress\": \"130.1.1.2\", \"subnet_mask\": \"255.255.255.0\", \"gateway\": \"130.1.1.255\", \"mac_address\": \"f6:de:1e:63:8e:7f\", \"speed\": 100, \"mtu\": 1500, \"wake_on_lan\": false, \"dns_servers\": [], \"enabled\": false}}, \"file_system\": {\"uuid\": \"ac9a6643-8349-4f7a-98c7-a1a9f97ce123\", \"folders\": {\"befa5d92-0878-4da2-9dac-f993c0b4a554\": {\"uuid\": \"befa5d92-0878-4da2-9dac-f993c0b4a554\", \"name\": \"static\", \"size\": 0, \"files\": {}, \"is_quarantined\": false}, \"27383b5e-8884-4ec0-bb50-a5d43e460dfa\": {\"uuid\": \"27383b5e-8884-4ec0-bb50-a5d43e460dfa\", \"name\": \"root\", \"size\": 40.0, \"files\": {\"253e4606-0f6d-4e57-8db0-6fa7e331ecea\": {\"uuid\": \"253e4606-0f6d-4e57-8db0-6fa7e331ecea\", \"name\": \"favicon.ico\", \"size\": 40.0, \"file_type\": \"PNG\"}}, \"is_quarantined\": false}}}, \"applications\": {}, \"services\": {}, \"process\": {}}}, \"links\": {\"a449b1ff-50d9-4342-861e-44f2d4dfef37\": {\"uuid\": \"a449b1ff-50d9-4342-861e-44f2d4dfef37\", \"endpoint_a\": \"09ca02eb-7733-492c-9eff-f0d6b6ebeeda\", \"endpoint_b\": \"ee4557d9-a309-45dd-a6e0-5b572cc70ee5\", \"bandwidth\": 100.0, \"current_load\": 0.0}, \"ebd7687b-ec69-4f1b-b2ba-86669aa95723\": {\"uuid\": \"ebd7687b-ec69-4f1b-b2ba-86669aa95723\", \"endpoint_a\": \"1abc7272-c516-4463-bd07-1a3cefe39313\", \"endpoint_b\": \"dc26b764-a07e-486a-99a4-798c8e0c187a\", \"bandwidth\": 100.0, \"current_load\": 0.0}}}, \"domain\": {\"uuid\": \"320cbb83-eb1b-4911-a4f0-fc46d8038a8a\", \"accounts\": {\"5fdcfb66-84f3-4f0f-a3a7-d0cb0e1a5d51\": {\"uuid\": \"5fdcfb66-84f3-4f0f-a3a7-d0cb0e1a5d51\", \"num_logons\": 0, \"num_logoffs\": 0, \"num_group_changes\": 0, \"username\": \"admin\", \"password\": \"admin12\", \"account_type\": \"USER\", \"enabled\": true}}}}'" + "'{\"uuid\": \"2ef348c6-32e5-4c5c-83b7-3b82d0b6123b\", \"network\": {\"uuid\": \"dd2d1a02-d461-4505-8bbd-fd0681750175\", \"nodes\": {\"2f03b32b-7290-4921-8670-faebe4a19d63\": {\"uuid\": \"2f03b32b-7290-4921-8670-faebe4a19d63\", \"hostname\": \"primaite_pc\", \"operating_state\": 0, \"NICs\": {\"e07e2a7f-b09f-4bd8-8e92-cffbf1f2270b\": {\"uuid\": \"e07e2a7f-b09f-4bd8-8e92-cffbf1f2270b\", \"ip_adress\": \"130.1.1.1\", \"subnet_mask\": \"255.255.255.0\", \"gateway\": \"130.1.1.255\", \"mac_address\": \"cc:be:ec:43:a6:4c\", \"speed\": 100, \"mtu\": 1500, \"wake_on_lan\": false, \"dns_servers\": [], \"enabled\": false}}, \"file_system\": {\"uuid\": \"0b7206af-3e0a-41b0-8115-ae9e0dbbcd81\", \"folders\": {\"c161bc7c-9abd-4666-9b49-2745fdb65ebe\": {\"uuid\": \"c161bc7c-9abd-4666-9b49-2745fdb65ebe\", \"name\": \"downloads\", \"size\": 1000.0, \"files\": {\"f807d777-d167-4f37-9f9b-ced634af6ed5\": {\"uuid\": \"f807d777-d167-4f37-9f9b-ced634af6ed5\", \"name\": \"firefox_installer.zip\", \"size\": 1000.0, \"file_type\": \"ZIP\"}}, \"is_quarantined\": false}}}, \"applications\": {\"ea466b2f-1ed5-49fd-9579-44852bff684d\": {\"uuid\": \"ea466b2f-1ed5-49fd-9579-44852bff684d\", \"health_state\": \"GOOD\", \"health_state_red_view\": \"GOOD\", \"criticality\": \"MEDIUM\", \"patching_count\": 0, \"scanning_count\": 0, \"revealed_to_red\": false, \"installing_count\": 0, \"max_sessions\": 1, \"tcp\": true, \"udp\": true, \"ports\": [\"HTTP\"], \"opearting_state\": \"RUNNING\", \"execution_control_status\": \"manual\", \"num_executions\": 0, \"groups\": []}}, \"services\": {}, \"process\": {}}, \"e9afc0bc-fb21-48a3-9868-2ede6a3181dc\": {\"uuid\": \"e9afc0bc-fb21-48a3-9868-2ede6a3181dc\", \"hostname\": \"google_server\", \"operating_state\": 0, \"NICs\": {\"956ce240-8fb3-4fde-8635-ac4ea601a582\": {\"uuid\": \"956ce240-8fb3-4fde-8635-ac4ea601a582\", \"ip_adress\": \"130.1.1.2\", \"subnet_mask\": \"255.255.255.0\", \"gateway\": \"130.1.1.255\", \"mac_address\": \"c2:1e:48:e1:a4:ad\", \"speed\": 100, \"mtu\": 1500, \"wake_on_lan\": false, \"dns_servers\": [], \"enabled\": false}}, \"file_system\": {\"uuid\": \"c3f99c30-b493-4fb6-b13e-d2005d851b59\", \"folders\": {\"869eda49-21f2-4fc1-8681-78725cdd5c70\": {\"uuid\": \"869eda49-21f2-4fc1-8681-78725cdd5c70\", \"name\": \"static\", \"size\": 0, \"files\": {}, \"is_quarantined\": false}, \"9fbe0e41-0d6a-4142-9c73-9c0de2dbde6e\": {\"uuid\": \"9fbe0e41-0d6a-4142-9c73-9c0de2dbde6e\", \"name\": \"root\", \"size\": 40.0, \"files\": {\"7d56a563-ecc0-4011-8c97-240dd6c885c0\": {\"uuid\": \"7d56a563-ecc0-4011-8c97-240dd6c885c0\", \"name\": \"favicon.ico\", \"size\": 40.0, \"file_type\": \"PNG\"}}, \"is_quarantined\": false}}}, \"applications\": {}, \"services\": {}, \"process\": {}}, \"47814452-ef47-4e6b-9087-796c438d4698\": {\"uuid\": \"47814452-ef47-4e6b-9087-796c438d4698\", \"num_ports\": 12, \"ports\": {\"1\": {\"uuid\": \"b76fe86f-bb92-4346-8e83-217a2fb0bc67\", \"mac_address\": \"79:2b:4a:70:c3:50\", \"speed\": 100, \"mtu\": 1500, \"enabled\": false}, \"2\": {\"uuid\": \"6f8fc6e7-76a4-441a-b7af-441edbdcc6ac\", \"mac_address\": \"1a:2d:12:38:80:2f\", \"speed\": 100, \"mtu\": 1500, \"enabled\": false}, \"3\": {\"uuid\": \"1aa75a3c-01f1-4293-9894-5396fa412690\", \"mac_address\": \"d1:7b:36:c1:82:c1\", \"speed\": 100, \"mtu\": 1500, \"enabled\": false}, \"4\": {\"uuid\": \"fe6c9f44-59d5-403e-973a-6f19fce7b9b9\", \"mac_address\": \"e3:6b:cc:0c:98:9b\", \"speed\": 100, \"mtu\": 1500, \"enabled\": false}, \"5\": {\"uuid\": \"e9e83e37-8537-4884-98a6-87017540078f\", \"mac_address\": \"32:09:c0:4a:f1:20\", \"speed\": 100, \"mtu\": 1500, \"enabled\": false}, \"6\": {\"uuid\": \"747f2cd3-8902-4da8-8829-b0b53fe79735\", \"mac_address\": \"e8:20:0b:04:b8:76\", \"speed\": 100, \"mtu\": 1500, \"enabled\": false}, \"7\": {\"uuid\": \"88ed129e-0ddb-4d29-ba3c-58d81efe240e\", \"mac_address\": \"7f:b4:f4:2e:b6:71\", \"speed\": 100, \"mtu\": 1500, \"enabled\": false}, \"8\": {\"uuid\": \"6c1a4c3c-25d8-46f6-98a8-54073d0ca0d3\", \"mac_address\": \"f6:22:2d:24:b9:71\", \"speed\": 100, \"mtu\": 1500, \"enabled\": false}, \"9\": {\"uuid\": \"b2bfc006-6a6b-4701-a75a-27954592d429\", \"mac_address\": \"b6:a5:92:a5:aa:1b\", \"speed\": 100, \"mtu\": 1500, \"enabled\": false}, \"10\": {\"uuid\": \"3c607386-87a2-4d0b-ac04-449416ca5b1f\", \"mac_address\": \"b3:75:7d:ce:88:0a\", \"speed\": 100, \"mtu\": 1500, \"enabled\": false}, \"11\": {\"uuid\": \"590002c8-27fa-4c31-b17b-7b89dbf8cdf8\", \"mac_address\": \"c0:25:a6:64:52:8e\", \"speed\": 100, \"mtu\": 1500, \"enabled\": false}, \"12\": {\"uuid\": \"b7e25eed-547a-4c17-8cb9-8b976ce4bbd9\", \"mac_address\": \"98:50:96:47:ca:bc\", \"speed\": 100, \"mtu\": 1500, \"enabled\": false}}, \"mac_address_table\": {}}}, \"links\": {\"a51a4435-20ae-43cf-a151-26e824968b3d\": {\"uuid\": \"a51a4435-20ae-43cf-a151-26e824968b3d\", \"endpoint_a\": \"e07e2a7f-b09f-4bd8-8e92-cffbf1f2270b\", \"endpoint_b\": \"b76fe86f-bb92-4346-8e83-217a2fb0bc67\", \"bandwidth\": 100.0, \"current_load\": 0.0}, \"ae3486e5-f78e-4092-96d1-d7e8176f2b7d\": {\"uuid\": \"ae3486e5-f78e-4092-96d1-d7e8176f2b7d\", \"endpoint_a\": \"956ce240-8fb3-4fde-8635-ac4ea601a582\", \"endpoint_b\": \"6f8fc6e7-76a4-441a-b7af-441edbdcc6ac\", \"bandwidth\": 100.0, \"current_load\": 0.0}}}, \"domain\": {\"uuid\": \"ae0423ee-51fa-41e7-be80-c642b39707f6\", \"accounts\": {\"917eda28-9a67-4449-bddd-87e2141a3162\": {\"uuid\": \"917eda28-9a67-4449-bddd-87e2141a3162\", \"num_logons\": 0, \"num_logoffs\": 0, \"num_group_changes\": 0, \"username\": \"admin\", \"password\": \"admin12\", \"account_type\": \"USER\", \"enabled\": true}}}}'" ] }, "execution_count": 16, diff --git a/src/primaite/simulator/core.py b/src/primaite/simulator/core.py index 2c802c0f..63120ecf 100644 --- a/src/primaite/simulator/core.py +++ b/src/primaite/simulator/core.py @@ -137,6 +137,7 @@ class SimComponent(BaseModel): kwargs["uuid"] = str(uuid4()) super().__init__(**kwargs) self.action_manager: Optional[ActionManager] = None + self._parent: Optional["SimComponent"] = None @abstractmethod def describe_state(self) -> Dict: @@ -187,3 +188,24 @@ class SimComponent(BaseModel): Override this method with anything that needs to happen within the component for it to be reset. """ pass + + @property + def parent(self) -> "SimComponent": + """Reference to the parent object which manages this object. + + :return: Parent object. + :rtype: SimComponent + """ + return self._parent + + @parent.setter + def parent(self, new_parent: "SimComponent") -> None: + if self._parent: + msg = f"Overwriting parent of {self}, {self._parent} with {new_parent}" + _LOGGER.warn(msg) + raise RuntimeWarning(msg) + self._parent = new_parent + + @parent.deleter + def parent(self) -> None: + self._parent = None diff --git a/src/primaite/simulator/network/container.py b/src/primaite/simulator/network/container.py index f89ed2d3..1c03358c 100644 --- a/src/primaite/simulator/network/container.py +++ b/src/primaite/simulator/network/container.py @@ -1,10 +1,13 @@ -from typing import Dict +from typing import Any, Dict, Union +from primaite import getLogger from primaite.simulator.core import Action, ActionManager, AllowAllValidator, SimComponent -from primaite.simulator.network.hardware.base import Link, Node +from primaite.simulator.network.hardware.base import Link, NIC, Node, SwitchPort + +_LOGGER = getLogger(__name__) -class NetworkContainer(SimComponent): +class Network(SimComponent): """Top level container object representing the physical network.""" nodes: Dict[str, Node] = {} @@ -40,3 +43,75 @@ class NetworkContainer(SimComponent): } ) return state + + def add_node(self, node: Node) -> None: + """ + Add an existing node to the network. + + :param node: Node instance that the network should keep track of. + :type node: Node + """ + if node in self: + msg = f"Can't add node {node}. It is already in the network." + _LOGGER.warning(msg) + raise RuntimeWarning(msg) + self.nodes[node.uuid] = node + node.parent = self + + def remove_node(self, node: Node) -> None: + """ + Remove a node from the network. + + :param node: Node instance that is currently part of the network that should be removed. + :type node: Node + """ + if node not in self: + msg = f"Can't remove node {node}. It's not in the network." + _LOGGER.warning(msg) + raise RuntimeWarning(msg) + del self.nodes[node.uuid] + del node.parent # misleading? + + def connect(self, endpoint_a: Union[NIC, SwitchPort], endpoint_b: Union[NIC, SwitchPort], **kwargs) -> None: + """Connect two nodes on the network by creating a link between an NIC/SwitchPort of each one. + + :param endpoint_a: The endpoint to which to connect the link on the first node + :type endpoint_a: Union[NIC, SwitchPort] + :param endpoint_b: The endpoint to which to connct the link on the second node + :type endpoint_b: Union[NIC, SwitchPort] + :raises RuntimeError: _description_ + """ + node_a = endpoint_a.parent + node_b = endpoint_b.parent + msg = "" + if node_a not in self: + msg = f"Cannot create a link to {endpoint_a} because the node is not in the network." + if node_b not in self: + msg = f"Cannot create a link to {endpoint_b} because the node is not in the network." + if node_a is node_b: + msg = f"Cannot link {endpoint_a} to {endpoint_b} because they belong to the same node." + if msg: + _LOGGER.error(msg) + raise RuntimeError(msg) + + link = Link(endpoint_a=endpoint_a, endpoint_b=endpoint_b, **kwargs) + self.links[link.uuid] = link + link.parent = self + + def remove_link(self, link: Link) -> None: + """Disconnect a link from the network. + + :param link: The link to be removed + :type link: Link + """ + link.endpoint_a.disconnect_link() + link.endpoint_b.disconnect_link() + del self.links[link.uuid] + del link.parent + + def __contains__(self, item: Any) -> bool: + if isinstance(item, Node): + return item.uuid in self.nodes + elif isinstance(item, Link): + return item.uuid in self.links + raise TypeError("") diff --git a/src/primaite/simulator/network/hardware/base.py b/src/primaite/simulator/network/hardware/base.py index 28e7693a..fe3b5b15 100644 --- a/src/primaite/simulator/network/hardware/base.py +++ b/src/primaite/simulator/network/hardware/base.py @@ -918,6 +918,7 @@ class Node(SimComponent): if nic.uuid not in self.nics: self.nics[nic.uuid] = nic nic.connected_node = self + nic.parent = self self.sys_log.info(f"Connected NIC {nic}") if self.operating_state == NodeOperatingState.ON: nic.enable() @@ -938,6 +939,7 @@ class Node(SimComponent): nic = self.nics.get(nic) if nic or nic.uuid in self.nics: self.nics.pop(nic.uuid) + del nic.parent nic.disable() self.sys_log.info(f"Disconnected NIC {nic}") else: @@ -1009,6 +1011,7 @@ class Switch(Node): self.switch_ports = {i: SwitchPort() for i in range(1, self.num_ports + 1)} for port_num, port in self.switch_ports.items(): port.connected_node = self + port.parent = self port.port_num = port_num def show(self): diff --git a/src/primaite/simulator/sim_container.py b/src/primaite/simulator/sim_container.py index 50fe412c..319defe4 100644 --- a/src/primaite/simulator/sim_container.py +++ b/src/primaite/simulator/sim_container.py @@ -2,19 +2,19 @@ from typing import Dict from primaite.simulator.core import Action, ActionManager, AllowAllValidator, SimComponent from primaite.simulator.domain.controller import DomainController -from primaite.simulator.network.container import NetworkContainer +from primaite.simulator.network.container import Network class Simulation(SimComponent): """Top-level simulation object which holds a reference to all other parts of the simulation.""" - network: NetworkContainer + network: Network domain: DomainController def __init__(self, **kwargs): """Initialise the Simulation.""" if not kwargs.get("network"): - kwargs["network"] = NetworkContainer() + kwargs["network"] = Network() if not kwargs.get("domain"): kwargs["domain"] = DomainController() diff --git a/tests/integration_tests/network/test_network_creation.py b/tests/integration_tests/network/test_network_creation.py new file mode 100644 index 00000000..418f5e5f --- /dev/null +++ b/tests/integration_tests/network/test_network_creation.py @@ -0,0 +1,82 @@ +import pytest + +from primaite.simulator.network.container import Network +from primaite.simulator.network.hardware.base import NIC, Node + + +def test_adding_removing_nodes(): + """Check that we can create and add a node to a network.""" + net = Network() + n1 = Node(hostname="computer") + net.add_node(n1) + assert n1.parent is net + assert n1 in net + + net.remove_node(n1) + assert n1.parent is None + assert n1 not in net + + +def test_readding_node(): + """Check that warning is raised when readding a node.""" + net = Network() + n1 = Node(hostname="computer") + net.add_node(n1) + with pytest.raises(RuntimeWarning): + net.add_node(n1) + assert n1.parent is net + assert n1 in net + + +def test_removing_nonexistent_node(): + """Check that warning is raised when trying to remove a node that is not in the network.""" + net = Network() + n1 = Node(hostname="computer") + with pytest.raises(RuntimeWarning): + net.remove_node(n1) + assert n1.parent is None + assert n1 not in net + + +def test_connecting_nodes(): + """Check that two nodes on the network can be connected.""" + net = Network() + n1 = Node(hostname="computer") + n1_nic = NIC(ip_address="120.30.0.1", gateway="192.168.0.1", subnet_mask="255.255.255.0") + n1.connect_nic(n1_nic) + n2 = Node(hostname="server") + n2_nic = NIC(ip_address="120.30.0.2", gateway="192.168.0.1", subnet_mask="255.255.255.0") + n2.connect_nic(n2_nic) + + net.add_node(n1) + net.add_node(n2) + + net.connect(n1.nics[n1_nic.uuid], n2.nics[n2_nic.uuid], bandwidth=30) + + assert len(net.links) == 1 + link = list(net.links.values())[0] + assert link in net + assert link.parent is net + + +def test_connecting_node_to_itself(): + net = Network() + node = Node(hostname="computer") + nic1 = NIC(ip_address="120.30.0.1", gateway="192.168.0.1", subnet_mask="255.255.255.0") + node.connect_nic(nic1) + nic2 = NIC(ip_address="120.30.0.2", gateway="192.168.0.1", subnet_mask="255.255.255.0") + node.connect_nic(nic2) + + net.add_node(node) + + with pytest.raises(RuntimeError): + net.connect(node.nics[nic1.uuid], node.nics[nic2.uuid], bandwidth=30) + + assert node in net + assert nic1.connected_link is None + assert nic2.connected_link is None + assert len(net.links) == 0 + + +def test_disconnecting_nodes(): + ... diff --git a/tests/unit_tests/_primaite/_simulator/_network/test_container.py b/tests/unit_tests/_primaite/_simulator/_network/test_container.py new file mode 100644 index 00000000..290e7cc3 --- /dev/null +++ b/tests/unit_tests/_primaite/_simulator/_network/test_container.py @@ -0,0 +1,17 @@ +import json + +from primaite.simulator.network.container import Network + + +def test_creating_container(): + """Check that we can create a network container""" + net = Network() + assert net.nodes == {} + assert net.links == {} + + +def test_describe_state(): + """Check that we can describe network state without raising errors, and that the result is JSON serialisable.""" + net = Network() + state = net.describe_state() + json.dumps(state) # if this function call raises an error, the test fails, state was not JSON-serialisable