illumio-pylo 0.3.11__py3-none-any.whl → 0.3.13__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. illumio_pylo/API/APIConnector.py +138 -106
  2. illumio_pylo/API/CredentialsManager.py +168 -3
  3. illumio_pylo/API/Explorer.py +619 -14
  4. illumio_pylo/API/JsonPayloadTypes.py +64 -4
  5. illumio_pylo/FilterQuery.py +892 -0
  6. illumio_pylo/Helpers/exports.py +1 -1
  7. illumio_pylo/LabelCommon.py +13 -3
  8. illumio_pylo/LabelDimension.py +109 -0
  9. illumio_pylo/LabelStore.py +97 -38
  10. illumio_pylo/WorkloadStore.py +58 -0
  11. illumio_pylo/__init__.py +9 -3
  12. illumio_pylo/cli/__init__.py +5 -2
  13. illumio_pylo/cli/commands/__init__.py +1 -0
  14. illumio_pylo/cli/commands/credential_manager.py +555 -4
  15. illumio_pylo/cli/commands/label_delete_unused.py +0 -3
  16. illumio_pylo/cli/commands/traffic_export.py +358 -0
  17. illumio_pylo/cli/commands/ui/credential_manager_ui/app.js +638 -0
  18. illumio_pylo/cli/commands/ui/credential_manager_ui/index.html +217 -0
  19. illumio_pylo/cli/commands/ui/credential_manager_ui/styles.css +581 -0
  20. illumio_pylo/cli/commands/update_pce_objects_cache.py +1 -2
  21. illumio_pylo/cli/commands/ven_duplicate_remover.py +79 -59
  22. illumio_pylo/cli/commands/workload_export.py +29 -0
  23. illumio_pylo/utilities/cli.py +4 -1
  24. illumio_pylo/utilities/health_monitoring.py +5 -1
  25. {illumio_pylo-0.3.11.dist-info → illumio_pylo-0.3.13.dist-info}/METADATA +2 -1
  26. {illumio_pylo-0.3.11.dist-info → illumio_pylo-0.3.13.dist-info}/RECORD +29 -24
  27. {illumio_pylo-0.3.11.dist-info → illumio_pylo-0.3.13.dist-info}/WHEEL +1 -1
  28. illumio_pylo/Query.py +0 -331
  29. {illumio_pylo-0.3.11.dist-info → illumio_pylo-0.3.13.dist-info}/licenses/LICENSE +0 -0
  30. {illumio_pylo-0.3.11.dist-info → illumio_pylo-0.3.13.dist-info}/top_level.txt +0 -0
@@ -1,9 +1,10 @@
1
1
  import sys
2
- from typing import Optional, List, Dict, Literal, TypeVar, Generic, Union
3
- from datetime import datetime, timedelta
2
+ from typing import Optional, List, Dict, Literal, TypeVar, Generic, Union, Set
3
+ from datetime import datetime, timedelta, timezone
4
4
 
5
5
  import illumio_pylo as pylo
6
- from .JsonPayloadTypes import RuleCoverageQueryEntryJsonStructure
6
+ from .JsonPayloadTypes import RuleCoverageQueryEntryJsonStructure, ExplorerTrafficRecordsApiReplyPayloadJsonStructure, \
7
+ ExplorerTrafficRecordJsonStructure
7
8
  from illumio_pylo.API.APIConnector import APIConnector
8
9
 
9
10
 
@@ -346,7 +347,6 @@ class ExplorerResultSetV1:
346
347
  draft_manager.execute()
347
348
 
348
349
 
349
-
350
350
  class RuleCoverageQueryManager:
351
351
 
352
352
  class QueryServices:
@@ -697,7 +697,11 @@ class RuleCoverageQueryManager:
697
697
 
698
698
  self.apply_policy_decisions_to_logs()
699
699
 
700
+
700
701
  class ExplorerFilterSetV1:
702
+ """
703
+ Legacy Filters for use with PCE version < 23.2"
704
+ """
701
705
  exclude_processes_emulate: Dict[str, str]
702
706
  _exclude_processes: List[str]
703
707
  _exclude_direct_services: List['pylo.DirectServiceInRule']
@@ -879,13 +883,6 @@ class ExplorerFilterSetV1:
879
883
  for item in map.to_list_of_cidr_string(skip_netmask_for_32=True):
880
884
  self.provider_exclude_cidr(item)
881
885
 
882
- def provider_include_cidr(self, ipaddress: str):
883
- self.__filter_provider_ip_include.append(ipaddress)
884
-
885
- def provider_include_ip4map(self, map: 'pylo.IP4Map'):
886
- for item in map.to_list_of_cidr_string():
887
- self.provider_include_cidr(item)
888
-
889
886
  def service_include_add(self, service: Union['pylo.DirectServiceInRule',str]):
890
887
  if isinstance(service, str):
891
888
  self._include_direct_services.append(pylo.DirectServiceInRule.create_from_text(service))
@@ -929,7 +926,7 @@ class ExplorerFilterSetV1:
929
926
  self._time_from = time
930
927
 
931
928
  def set_time_from_x_seconds_ago(self, seconds: int):
932
- self._time_from = datetime.utcnow() - timedelta(seconds=seconds)
929
+ self._time_from = datetime.now(timezone.utc) - timedelta(seconds=seconds)
933
930
 
934
931
  def set_time_from_x_days_ago(self, days: int):
935
932
  return self.set_time_from_x_seconds_ago(days*60*60*24)
@@ -941,7 +938,7 @@ class ExplorerFilterSetV1:
941
938
  self._time_to = time
942
939
 
943
940
  def set_time_to_x_seconds_ago(self, seconds: int):
944
- self._time_to = datetime.utcnow() - timedelta(seconds=seconds)
941
+ self._time_to = datetime.now(timezone.utc) - timedelta(seconds=seconds)
945
942
 
946
943
  def set_time_to_x_days_ago(self, days: int):
947
944
  return self.set_time_to_x_seconds_ago(days*60*60*24)
@@ -1091,6 +1088,12 @@ class ExplorerFilterSetV1:
1091
1088
  for process in self._exclude_processes:
1092
1089
  filters['services']['exclude'].append({'process_name': process})
1093
1090
 
1091
+ # empty sources/destinations include will return no results, so we add an empty array to mean "any"
1092
+ if len(filters['sources']['include']) == 0:
1093
+ filters['sources']['include'].append([])
1094
+ if len(filters['destinations']['include']) == 0:
1095
+ filters['destinations']['include'].append([])
1096
+
1094
1097
  # print(filters)
1095
1098
  return filters
1096
1099
 
@@ -1104,7 +1107,6 @@ class ExplorerQuery:
1104
1107
  self.max_running_time_seconds = max_running_time_seconds
1105
1108
  self.check_for_update_interval_seconds = check_for_update_interval_seconds
1106
1109
 
1107
-
1108
1110
  def execute(self) -> ExplorerResultSetV1:
1109
1111
  """
1110
1112
  Execute the query and stores the results in the 'results' property.
@@ -1116,6 +1118,609 @@ class ExplorerQuery:
1116
1118
  return self.results
1117
1119
 
1118
1120
 
1121
+ class ExplorerFilterSetV2:
1122
+ """
1123
+ New Filters for use with PCE version >= 23.2"
1124
+ """
1125
+ def __init__(self, max_results=2500):
1126
+ self._source_filters: List[ExplorerFilterSetV2Filter] = [] # List of source filters, in the backend logic they will be treated with OR logic
1127
+ self._destination_filters: List[ExplorerFilterSetV2Filter] = [] # List of destination filters, in the backend logic they will be treated with OR logic
1128
+
1129
+ self.__filter_consumer_ip_exclude = []
1130
+ self.__filter_provider_ip_exclude = []
1131
+
1132
+ self._consumer_exclude_labels: Dict[str, Union[pylo.Label, pylo.LabelGroup]] = {}
1133
+ self._provider_exclude_labels: Dict[str, Union[pylo.Label, pylo.LabelGroup]] = {}
1134
+
1135
+ self._consumer_iplists_exclude = {}
1136
+ self._provider_iplists_exclude = {}
1137
+
1138
+ self.max_results = max_results
1139
+
1140
+ self._policy_decision_filter = []
1141
+ self._time_from = None
1142
+ self._time_to = None
1143
+
1144
+ self._include_direct_services = []
1145
+
1146
+ self._exclude_broadcast = False
1147
+ self._exclude_multicast = False
1148
+ self._exclude_direct_services = []
1149
+ self._exclude_processes = []
1150
+
1151
+ @staticmethod
1152
+ def __filter_prop_add_label(prop_dict, label_or_href):
1153
+ """
1154
+
1155
+ @type prop_dict: dict
1156
+ @type label_or_href: str|pylo.Label|pylo.LabelGroup
1157
+ """
1158
+ if isinstance(label_or_href, str):
1159
+ prop_dict[label_or_href] = label_or_href
1160
+ return
1161
+ elif isinstance(label_or_href, pylo.Label):
1162
+ prop_dict[label_or_href.href] = label_or_href
1163
+ return
1164
+ elif isinstance(label_or_href, pylo.LabelGroup):
1165
+ # since 21.5 labelgroups can be included directly
1166
+ # for nested_label in label_or_href.expand_nested_to_array():
1167
+ # prop_dict[nested_label.href] = nested_label
1168
+ prop_dict[label_or_href.href] = label_or_href
1169
+ return
1170
+ else:
1171
+ raise pylo.PyloEx("Unsupported object type {}".format(type(label_or_href)))
1172
+
1173
+ def new_source_filter(self) -> 'ExplorerFilterSetV2Filter':
1174
+ filter = ExplorerFilterSetV2Filter()
1175
+ self._source_filters.append(filter)
1176
+ return filter
1177
+
1178
+ def new_destination_filter(self) -> 'ExplorerFilterSetV2Filter':
1179
+ filter = ExplorerFilterSetV2Filter()
1180
+ self._destination_filters.append(filter)
1181
+ return filter
1182
+
1183
+ def consumer_exclude_label(self, label_or_href: Union[str, 'pylo.Label', 'pylo.LabelGroup']):
1184
+ self.__filter_prop_add_label(self._consumer_exclude_labels, label_or_href)
1185
+
1186
+ def consumer_exclude_labels(self, labels: List[Union[str, 'pylo.Label', 'pylo.LabelGroup']]):
1187
+ for label in labels:
1188
+ self.consumer_exclude_label(label)
1189
+
1190
+
1191
+ def consumer_exclude_cidr(self, ipaddress: str):
1192
+ self.__filter_consumer_ip_exclude.append(ipaddress)
1193
+
1194
+ def consumer_exclude_iplist(self, iplist_or_href: Union[str, 'pylo.IPList']):
1195
+ if isinstance(iplist_or_href, str):
1196
+ self._consumer_iplists_exclude[iplist_or_href] = iplist_or_href
1197
+ return
1198
+
1199
+ if isinstance(iplist_or_href, pylo.IPList):
1200
+ self._consumer_iplists_exclude[iplist_or_href.href] = iplist_or_href.href
1201
+ return
1202
+
1203
+ raise pylo.PyloEx("Unsupported object type {}".format(type(iplist_or_href)))
1204
+
1205
+ def consumer_exclude_ip4map(self, map: 'pylo.IP4Map'):
1206
+ for item in map.to_list_of_cidr_string():
1207
+ self.consumer_exclude_cidr(item)
1208
+
1209
+ def provider_exclude_label(self, label_or_href: Union[str, 'pylo.Label', 'pylo.LabelGroup']):
1210
+ self.__filter_prop_add_label(self._provider_exclude_labels, label_or_href)
1211
+
1212
+ def provider_exclude_labels(self, labels_or_hrefs: List[Union[str, 'pylo.Label', 'pylo.LabelGroup']]):
1213
+ for label in labels_or_hrefs:
1214
+ self.provider_exclude_label(label)
1215
+
1216
+ def provider_exclude_cidr(self, ipaddress: str):
1217
+ self.__filter_provider_ip_exclude.append(ipaddress)
1218
+
1219
+ def provider_exclude_iplist(self, iplist_or_href: Union[str, 'pylo.IPList']):
1220
+ if isinstance(iplist_or_href, str):
1221
+ self._provider_iplists_exclude[iplist_or_href] = iplist_or_href
1222
+ return
1223
+
1224
+ if isinstance(iplist_or_href, pylo.IPList):
1225
+ self._provider_iplists_exclude[iplist_or_href.href] = iplist_or_href.href
1226
+ return
1227
+
1228
+ raise pylo.PyloEx("Unsupported object type {}".format(type(iplist_or_href)))
1229
+
1230
+ def provider_exclude_ip4map(self, map: 'pylo.IP4Map'):
1231
+ for item in map.to_list_of_cidr_string(skip_netmask_for_32=True):
1232
+ self.provider_exclude_cidr(item)
1233
+
1234
+ def service_include_add(self, service: Union['pylo.DirectServiceInRule',str]):
1235
+ if isinstance(service, str):
1236
+ self._include_direct_services.append(pylo.DirectServiceInRule.create_from_text(service))
1237
+ return
1238
+ self._include_direct_services.append(service)
1239
+
1240
+ def service_include_add_protocol(self, protocol: int):
1241
+ self._include_direct_services.append(pylo.DirectServiceInRule(proto=protocol))
1242
+
1243
+ def service_include_add_protocol_tcp(self):
1244
+ self._include_direct_services.append(pylo.DirectServiceInRule(proto=6))
1245
+
1246
+ def service_include_add_protocol_udp(self):
1247
+ self._include_direct_services.append(pylo.DirectServiceInRule(proto=17))
1248
+
1249
+ def service_exclude_add(self, service: 'pylo.DirectServiceInRule'):
1250
+ self._exclude_direct_services.append(service)
1251
+
1252
+ def service_exclude_add_protocol(self, protocol: int):
1253
+ self._exclude_direct_services.append(pylo.DirectServiceInRule(proto=protocol))
1254
+
1255
+ def service_exclude_add_protocol_tcp(self):
1256
+ self._exclude_direct_services.append(pylo.DirectServiceInRule(proto=6))
1257
+
1258
+ def service_exclude_add_protocol_udp(self):
1259
+ self._exclude_direct_services.append(pylo.DirectServiceInRule(proto=17))
1260
+
1261
+ def process_exclude_add(self, process_name: str, emulate_on_client=False):
1262
+ if emulate_on_client:
1263
+ self.exclude_processes_emulate[process_name] = process_name
1264
+ else:
1265
+ self._exclude_processes.append(process_name)
1266
+
1267
+ def set_exclude_broadcast(self, exclude=True):
1268
+ self._exclude_broadcast = exclude
1269
+
1270
+ def set_exclude_multicast(self, exclude=True):
1271
+ self._exclude_multicast = exclude
1272
+
1273
+ def set_time_from(self, time: datetime):
1274
+ self._time_from = time
1275
+
1276
+ def set_time_from_x_seconds_ago(self, seconds: int):
1277
+ self._time_from = datetime.now(timezone.utc) - timedelta(seconds=seconds)
1278
+
1279
+ def set_time_from_x_days_ago(self, days: int):
1280
+ return self.set_time_from_x_seconds_ago(days*60*60*24)
1281
+
1282
+ def set_max_results(self, max: int):
1283
+ self.max_results = max
1284
+
1285
+ def set_time_to(self, time: datetime):
1286
+ self._time_to = time
1287
+
1288
+ def set_time_to_x_seconds_ago(self, seconds: int):
1289
+ self._time_to = datetime.now(timezone.utc) - timedelta(seconds=seconds)
1290
+
1291
+ def set_time_to_x_days_ago(self, days: int):
1292
+ return self.set_time_to_x_seconds_ago(days*60*60*24)
1293
+
1294
+ def filter_on_policy_decision_unknown(self):
1295
+ self._policy_decision_filter.append('unknown')
1296
+
1297
+ def filter_on_policy_decision_blocked(self):
1298
+ self._policy_decision_filter.append('blocked')
1299
+
1300
+ def filter_on_policy_decision_potentially_blocked(self):
1301
+ self._policy_decision_filter.append('potentially_blocked')
1302
+
1303
+ def filter_on_policy_decision_all_blocked(self):
1304
+ self.filter_on_policy_decision_blocked()
1305
+ self.filter_on_policy_decision_potentially_blocked()
1306
+
1307
+ def filter_on_policy_decision_allowed(self):
1308
+ self._policy_decision_filter.append('allowed')
1309
+
1310
+ def generate_json_query(self):
1311
+ """
1312
+ Generate the JSON query payload for this filter set
1313
+ :return:
1314
+ """
1315
+ filters = {
1316
+ "sources": {"include": [], "exclude": []},
1317
+ "destinations": {"include": [], "exclude": []},
1318
+ "services": {"include": [], "exclude": []},
1319
+ "sources_destinations_query_op": "and",
1320
+ "policy_decisions": self._policy_decision_filter,
1321
+ "max_results": self.max_results,
1322
+ "query_name": "api call"
1323
+ }
1324
+
1325
+ if self._exclude_broadcast:
1326
+ filters['destinations']['exclude'].append({'transmission': 'broadcast'})
1327
+
1328
+ if self._exclude_multicast:
1329
+ filters['destinations']['exclude'].append({'transmission': 'multicast'})
1330
+
1331
+ if self._time_from is not None:
1332
+ filters["start_date"] = self._time_from.strftime('%Y-%m-%dT%H:%M:%SZ')
1333
+ else:
1334
+ filters["start_date"] = "2010-10-13T11:27:28.824Z",
1335
+
1336
+ if self._time_to is not None:
1337
+ filters["end_date"] = self._time_to.strftime('%Y-%m-%dT%H:%M:%SZ')
1338
+ else:
1339
+ filters["end_date"] = datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ')
1340
+
1341
+ if len(self._consumer_exclude_labels) > 0:
1342
+ for label_href in self._consumer_exclude_labels.keys():
1343
+ filters['sources']['exclude'].append({'label': {'href': label_href}})
1344
+
1345
+ if len(self._consumer_iplists_exclude) > 0:
1346
+ for iplist_href in self._consumer_iplists_exclude.keys():
1347
+ filters['sources']['exclude'].append({'ip_list': {'href': iplist_href}})
1348
+
1349
+ if len(self.__filter_consumer_ip_exclude) > 0:
1350
+ for ipaddress in self.__filter_consumer_ip_exclude:
1351
+ filters['sources']['exclude'].append({'ip_address': ipaddress})
1352
+
1353
+ if len(self._provider_exclude_labels) > 0:
1354
+ for label_href in self._provider_exclude_labels.keys():
1355
+ filters['destinations']['exclude'].append({'label': {'href': label_href}})
1356
+
1357
+ if len(self._provider_iplists_exclude) > 0:
1358
+ for iplist_href in self._provider_iplists_exclude.keys():
1359
+ filters['destinations']['exclude'].append({'ip_list': {'href': iplist_href}})
1360
+
1361
+ if len(self.__filter_provider_ip_exclude) > 0:
1362
+ for ipaddress in self.__filter_provider_ip_exclude:
1363
+ filters['destinations']['exclude'].append({'ip_address': ipaddress})
1364
+
1365
+ if len(self._include_direct_services) > 0:
1366
+ for service in self._include_direct_services:
1367
+ filters['services']['include'] .append(service.get_api_json())
1368
+
1369
+ if len(self._exclude_direct_services) > 0:
1370
+ for service in self._exclude_direct_services:
1371
+ filters['services']['exclude'].append(service.get_api_json())
1372
+
1373
+ if len(self._exclude_processes) > 0:
1374
+ for process in self._exclude_processes:
1375
+ filters['services']['exclude'].append({'process_name': process})
1376
+
1377
+ for source_filter in self._source_filters:
1378
+ source_payloads = source_filter.generate_json_payloads()
1379
+ for payload in source_payloads:
1380
+ filters['sources']['include'].append(payload)
1381
+
1382
+ for destination_filter in self._destination_filters:
1383
+ destination_payloads = destination_filter.generate_json_payloads()
1384
+ for payload in destination_payloads:
1385
+ filters['destinations']['include'].append(payload)
1386
+
1387
+ # empty sources/destinations include will return no results, so we add an empty array to mean "any"
1388
+ if len(filters['sources']['include']) == 0:
1389
+ filters['sources']['include'].append([])
1390
+ if len(filters['destinations']['include']) == 0:
1391
+ filters['destinations']['include'].append([])
1392
+
1393
+ return filters
1394
+
1395
+
1396
+ class ExplorerFilterSetV2Filter:
1397
+ """
1398
+ A single filter for ExplorerFilterSetV2. The filter can contain multiple types items which will be treated differently
1399
+ """
1400
+ def __init__(self):
1401
+ self._labels_href_by_type: Dict[str, Set[str]] = {} # key is label type (e.g., 'environment'), value is set of label hrefs
1402
+ self._workloads_href: Set[str] = set()
1403
+ self._iplists_href: Set[str] = set()
1404
+
1405
+ def add_label(self, label: Union['pylo.Label', 'pylo.LabelGroup']):
1406
+ label_type = label.type
1407
+ if label_type not in self._labels_href_by_type:
1408
+ self._labels_href_by_type[label_type] = set()
1409
+ self._labels_href_by_type[label_type].add(label.href)
1410
+
1411
+ def add_workload(self, workload: 'pylo.Workload'):
1412
+ self._workloads_href.add(workload.href)
1413
+
1414
+ def add_iplist(self, iplist: 'pylo.IPList'):
1415
+ self._iplists_href.add(iplist.href)
1416
+
1417
+ def generate_json_payloads(self):
1418
+ """
1419
+ Generate the JSON payloads for this filter. It may return more than 1 payload in the result array because some items
1420
+ like labels of different types combination require a cartesian product to be generated.
1421
+ :return:
1422
+ """
1423
+ result_payloads = []
1424
+
1425
+ # First, generate all combinations of labels by type
1426
+ label_type_keys = list(self._labels_href_by_type.keys())
1427
+ label_combinations = [[]]
1428
+ for label_type in label_type_keys:
1429
+ new_combinations = []
1430
+ for href in self._labels_href_by_type[label_type]:
1431
+ for existing_combination in label_combinations:
1432
+ new_combination = existing_combination + [(label_type, href)]
1433
+ new_combinations.append(new_combination)
1434
+ label_combinations = new_combinations
1435
+
1436
+ if len(label_combinations) == 0:
1437
+ label_combinations = [[]] # Ensure at least one combination exists so payloads can be generated when no labels are present
1438
+
1439
+ for label_combination in label_combinations:
1440
+ payload = []
1441
+ for label_type, href in label_combination:
1442
+ payload.append({'label': {'type': label_type, 'href': href}})
1443
+
1444
+ for workload_href in self._workloads_href:
1445
+ payload.append({'workload': {'href': workload_href}})
1446
+ for iplist_href in self._iplists_href:
1447
+ payload.append({'ip_list': {'href': iplist_href}})
1448
+
1449
+ result_payloads.append(payload)
1450
+
1451
+ return result_payloads
1452
+
1453
+
1454
+ class ExplorerResultV2:
1455
+
1456
+ def __init__(self, data: ExplorerTrafficRecordJsonStructure):
1457
+ self.raw_json = data
1458
+ self.num_connections = data['num_connections']
1459
+
1460
+ self.policy_decision_string = data['policy_decision']
1461
+ self._draft_mode_policy_decision = data.get('draft_policy_decision')
1462
+
1463
+ self.source_ip_fqdn: Optional[str] = None
1464
+ self.destination_ip_fqdn: Optional[str] = None
1465
+
1466
+ src = data['src']
1467
+ self.source_ip: str = src['ip']
1468
+ self._source_iplists = src.get('ip_lists')
1469
+ self._source_iplists_href: List[str] = []
1470
+ if self._source_iplists is not None:
1471
+ for href in self._source_iplists:
1472
+ self._source_iplists_href.append(href['href'])
1473
+
1474
+ self.source_workload_href: Optional[str] = None
1475
+ self.source_workload_hostname: Optional[str] = None
1476
+ self.source_workload_labels_by_type: Dict[str, str] = {} # key is label type, value is label name
1477
+ workload_data = src.get('workload')
1478
+ if workload_data is not None:
1479
+ self.source_workload_href: Optional[str] = workload_data.get('href')
1480
+ if self.source_workload_href is None:
1481
+ raise pylo.PyloApiUnexpectedSyntax("Explorer API has return a record referring to a Workload with no HREF given:", data)
1482
+
1483
+ self.source_workload_hostname = workload_data.get('hostname')
1484
+
1485
+ self.source_workload_labels_href: Optional[List[str]] = []
1486
+ workload_labels_data = workload_data.get('labels')
1487
+ if workload_labels_data is not None:
1488
+ for label_data in workload_labels_data:
1489
+ self.source_workload_labels_href.append(label_data.get('href'))
1490
+ label_type = label_data.get('key')
1491
+ label_name = label_data.get('value')
1492
+ if label_type is not None and label_name is not None:
1493
+ self.source_workload_labels_by_type[label_type] = label_name
1494
+
1495
+ dst = data['dst']
1496
+ self.destination_ip: str = dst['ip']
1497
+ self.destination_ip_fqdn = dst.get('fqdn')
1498
+ self._destination_iplists = dst.get('ip_lists')
1499
+ self._destination_iplists_href: List[str] = []
1500
+ if self._destination_iplists is not None:
1501
+ for href in self._destination_iplists:
1502
+ self._destination_iplists_href.append(href['href'])
1503
+
1504
+ self.destination_workload_href: Optional[str] = None
1505
+ self.destination_workload_hostname: Optional[str] = None
1506
+ self.destination_workload_labels_by_type: Dict[str, str] = {} # key is label type, value is label name
1507
+ workload_data = dst.get('workload')
1508
+ if workload_data is not None:
1509
+ self.destination_workload_href = workload_data.get('href')
1510
+ if self.destination_workload_href is None:
1511
+ raise pylo.PyloApiUnexpectedSyntax("Explorer API has return a record referring to a Workload with no HREF given:", data)
1512
+
1513
+ self.destination_workload_hostname = workload_data.get('hostname')
1514
+
1515
+ self.destination_workload_labels_href: Optional[List[str]] = []
1516
+ workload_labels_data = workload_data.get('labels')
1517
+ if workload_labels_data is not None:
1518
+ for label_data in workload_labels_data:
1519
+ self.destination_workload_labels_href.append(label_data.get('href'))
1520
+ label_type = label_data.get('key')
1521
+ label_name = label_data.get('value')
1522
+ if label_type is not None and label_name is not None:
1523
+ self.destination_workload_labels_by_type[label_type] = label_name
1524
+
1525
+ service_json = data['service']
1526
+ self.service_json = service_json
1527
+
1528
+ self.service_protocol: int = service_json['proto']
1529
+ self.service_port: Optional[int] = service_json.get('port')
1530
+ self.process_name: Optional[str] = service_json.get('process_name')
1531
+ self.username: Optional[str] = service_json.get('user_name')
1532
+
1533
+ self.first_detected: str = data['timestamp_range']['first_detected'] # e.g., "2023-10-05T12:34:56Z" ISO 8601
1534
+ self.last_detected: str = data['timestamp_range']['last_detected'] # e.g., "2023-10-05T12:39:56Z" ISO 8601
1535
+
1536
+ self._cast_type: Optional[str] = data.get('transmission')
1537
+
1538
+ def service_to_str(self, protocol_first=True):
1539
+ if protocol_first:
1540
+ if self.service_port is None or self.service_port == 0:
1541
+ return 'proto/{}'.format(self.service_protocol)
1542
+
1543
+ if self.service_protocol == 17:
1544
+ return 'udp/{}'.format(self.service_port)
1545
+
1546
+ if self.service_protocol == 6:
1547
+ return 'tcp/{}'.format(self.service_port)
1548
+ else:
1549
+ if self.service_port is None or self.service_port == 0:
1550
+ return '{}/proto'.format(self.service_protocol)
1551
+
1552
+ if self.service_protocol == 17:
1553
+ return '{}/udp'.format(self.service_port)
1554
+
1555
+ if self.service_protocol == 6:
1556
+ return '{}/tcp'.format(self.service_port)
1557
+
1558
+ def service_to_str_array(self):
1559
+ if self.service_port is None or self.service_port == 0:
1560
+ return [self.service_protocol, 'proto']
1561
+
1562
+ if self.service_protocol == 17:
1563
+ return [self.service_port, 'udp']
1564
+
1565
+ if self.service_protocol == 6:
1566
+ return [self.service_port, 'tcp']
1567
+
1568
+ return ['n/a', 'n/a']
1569
+
1570
+ def source_is_workload(self):
1571
+ return self.source_workload_href is not None
1572
+
1573
+ def destination_is_workload(self):
1574
+ return self.destination_workload_href is not None
1575
+
1576
+ def get_source_workload_href(self):
1577
+ return self.source_workload_href
1578
+
1579
+ def get_destination_workload_href(self):
1580
+ return self.destination_workload_href
1581
+
1582
+ def get_source_workload(self, org_for_resolution: 'pylo.Organization') -> Optional['pylo.Workload']:
1583
+ if self.source_workload_href is None:
1584
+ return None
1585
+ return org_for_resolution.WorkloadStore.find_by_href_or_create_tmp(self.source_workload_href, '*DELETED*')
1586
+
1587
+ def get_destination_workload(self, org_for_resolution: 'pylo.Organization') -> Optional['pylo.Workload']:
1588
+ if self.destination_workload_href is None:
1589
+ return None
1590
+ return org_for_resolution.WorkloadStore.find_by_href_or_create_tmp(self.destination_workload_href, '*DELETED*')
1591
+
1592
+ def get_source_labels_href(self) -> Optional[List[str]]:
1593
+ if not self.source_is_workload():
1594
+ return None
1595
+ return self.source_workload_labels_href
1596
+
1597
+ def get_destination_labels_href(self) -> Optional[List[str]]:
1598
+ if not self.destination_is_workload():
1599
+ return None
1600
+ return self.destination_workload_labels_href
1601
+
1602
+ def get_source_iplists(self, org_for_resolution: 'pylo.Organization') ->Dict[str, 'pylo.IPList']:
1603
+ if self._source_iplists is None:
1604
+ return {}
1605
+
1606
+ result = {}
1119
1607
 
1608
+ for record in self._source_iplists:
1609
+ href = record.get('href')
1610
+ if href is None:
1611
+ raise pylo.PyloEx('Cannot find HREF for IPList in Explorer result json', record)
1612
+ iplist = org_for_resolution.IPListStore.find_by_href(href)
1613
+ if iplist is None:
1614
+ raise pylo.PyloEx('Cannot find HREF for IPList in Explorer result json', record)
1120
1615
 
1616
+ result[href] = iplist
1617
+
1618
+ return result
1619
+
1620
+ def get_source_iplists_href(self) -> Optional[List[str]]:
1621
+ if self.source_is_workload():
1622
+ return None
1623
+ if self._source_iplists_href is None:
1624
+ return []
1625
+ return self._source_iplists_href.copy()
1626
+
1627
+ def get_destination_iplists_href(self) -> Optional[List[str]]:
1628
+ if self.destination_is_workload():
1629
+ return None
1630
+
1631
+ if self._destination_iplists_href is None:
1632
+ return []
1633
+ return self._destination_iplists_href.copy()
1634
+
1635
+ def get_destination_iplists(self, org_for_resolution: 'pylo.Organization') -> Dict[str, 'pylo.IPList']:
1636
+ if self._destination_iplists is None:
1637
+ return {}
1638
+
1639
+ result = {}
1640
+
1641
+ for record in self._destination_iplists:
1642
+ href = record.get('href')
1643
+ if href is None:
1644
+ raise pylo.PyloEx('Cannot find HREF for IPList in Explorer result json', record)
1645
+ iplist = org_for_resolution.IPListStore.find_by_href(href)
1646
+ if iplist is None:
1647
+ raise pylo.PyloEx('Cannot find HREF for IPList in Explorer result json', record)
1648
+
1649
+ result[href] = iplist
1650
+
1651
+ return result
1652
+
1653
+ def pd_is_potentially_blocked(self):
1654
+ return self.policy_decision_string == 'potentially_blocked'
1655
+
1656
+ def cast_is_broadcast(self):
1657
+ return self._cast_type == 'broadcast'
1658
+
1659
+ def cast_is_multicast(self):
1660
+ return self._cast_type == 'multicast'
1661
+
1662
+ def cast_is_unicast(self):
1663
+ return self._cast_type is not None
1664
+
1665
+ def draft_mode_policy_decision_is_blocked(self) -> Optional[bool]:
1666
+ """
1667
+ @return: None if draft_mode was not enabled
1668
+ """
1669
+ return self._draft_mode_policy_decision is not None and \
1670
+ (self._draft_mode_policy_decision == 'blocked' or self._draft_mode_policy_decision == 'blocked_by_boundary')
1671
+
1672
+ def draft_mode_policy_decision_is_allowed(self) -> Optional[bool]:
1673
+ """
1674
+ @return: None if draft_mode was not enabled
1675
+ """
1676
+ return self._draft_mode_policy_decision is not None and self._draft_mode_policy_decision == "allowed"
1677
+
1678
+ def draft_mode_policy_decision_is_unavailable(self) -> Optional[bool]:
1679
+ """
1680
+ @return: None if draft_mode was not enabled
1681
+ """
1682
+ return self._draft_mode_policy_decision is None
1683
+
1684
+ def draft_mode_policy_decision_is_not_defined(self) -> Optional[bool]:
1685
+ return self._draft_mode_policy_decision is None
1686
+
1687
+ def draft_mode_policy_decision_to_str(self) -> str:
1688
+ if self._draft_mode_policy_decision is None:
1689
+ return 'not_available'
1690
+ return self._draft_mode_policy_decision
1691
+
1692
+
1693
+ class ExplorerResultSetV2:
1694
+ def __init__(self, data_array: ExplorerTrafficRecordsApiReplyPayloadJsonStructure):
1695
+ self.raw_json = data_array
1696
+ self.records: List[ExplorerResultV2] = []
1697
+ for record_json in data_array:
1698
+ record = ExplorerResultV2(record_json)
1699
+ self.records.append(record)
1700
+
1701
+ def get_all_records(self) -> List[ExplorerResultV2]:
1702
+ return self.records
1703
+
1704
+
1705
+ class ExplorerQueryV2:
1706
+ def __init__(self, connector: APIConnector, max_results: int = 1500, draft_mode_enabled=False, max_running_time_seconds: int = 1800,
1707
+ check_for_update_interval_seconds: int = 10):
1708
+ self.api: APIConnector = connector
1709
+ self.filters = ExplorerFilterSetV2(max_results=max_results)
1710
+ self.max_running_time_seconds = max_running_time_seconds
1711
+ self.check_for_update_interval_seconds = check_for_update_interval_seconds
1712
+ self.draft_mode_enabled = draft_mode_enabled
1713
+
1714
+ def execute(self) -> Union[ExplorerResultSetV2]:
1715
+ """
1716
+ Execute the query and stores the results in the 'results' property.
1717
+ It will also return said results for convenience.
1718
+ :return:
1719
+ """
1720
+ self.results = self.api.explorer_search(self.filters, max_running_time_seconds=self.max_running_time_seconds,
1721
+ check_for_update_interval_seconds=self.check_for_update_interval_seconds,
1722
+ draft_mode_enabled=self.draft_mode_enabled)
1723
+
1724
+
1725
+ return self.results
1121
1726