illumio-pylo 0.2.5__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.
- illumio_pylo/API/APIConnector.py +1308 -0
- illumio_pylo/API/AuditLog.py +42 -0
- illumio_pylo/API/ClusterHealth.py +136 -0
- illumio_pylo/API/CredentialsManager.py +286 -0
- illumio_pylo/API/Explorer.py +1077 -0
- illumio_pylo/API/JsonPayloadTypes.py +240 -0
- illumio_pylo/API/RuleSearchQuery.py +128 -0
- illumio_pylo/API/__init__.py +0 -0
- illumio_pylo/AgentStore.py +139 -0
- illumio_pylo/Exception.py +44 -0
- illumio_pylo/Helpers/__init__.py +3 -0
- illumio_pylo/Helpers/exports.py +508 -0
- illumio_pylo/Helpers/functions.py +166 -0
- illumio_pylo/IPList.py +135 -0
- illumio_pylo/IPMap.py +285 -0
- illumio_pylo/Label.py +25 -0
- illumio_pylo/LabelCommon.py +48 -0
- illumio_pylo/LabelGroup.py +68 -0
- illumio_pylo/LabelStore.py +403 -0
- illumio_pylo/LabeledObject.py +25 -0
- illumio_pylo/Organization.py +258 -0
- illumio_pylo/Query.py +331 -0
- illumio_pylo/ReferenceTracker.py +41 -0
- illumio_pylo/Rule.py +671 -0
- illumio_pylo/Ruleset.py +306 -0
- illumio_pylo/RulesetStore.py +101 -0
- illumio_pylo/SecurityPrincipal.py +62 -0
- illumio_pylo/Service.py +256 -0
- illumio_pylo/SoftwareVersion.py +125 -0
- illumio_pylo/VirtualService.py +17 -0
- illumio_pylo/VirtualServiceStore.py +75 -0
- illumio_pylo/Workload.py +506 -0
- illumio_pylo/WorkloadStore.py +289 -0
- illumio_pylo/__init__.py +82 -0
- illumio_pylo/cli/NativeParsers.py +96 -0
- illumio_pylo/cli/__init__.py +134 -0
- illumio_pylo/cli/__main__.py +10 -0
- illumio_pylo/cli/commands/__init__.py +32 -0
- illumio_pylo/cli/commands/credential_manager.py +168 -0
- illumio_pylo/cli/commands/iplist_import_from_file.py +185 -0
- illumio_pylo/cli/commands/misc.py +7 -0
- illumio_pylo/cli/commands/ruleset_export.py +129 -0
- illumio_pylo/cli/commands/update_pce_objects_cache.py +44 -0
- illumio_pylo/cli/commands/ven_duplicate_remover.py +366 -0
- illumio_pylo/cli/commands/ven_idle_to_visibility.py +287 -0
- illumio_pylo/cli/commands/ven_upgrader.py +226 -0
- illumio_pylo/cli/commands/workload_export.py +251 -0
- illumio_pylo/cli/commands/workload_import.py +423 -0
- illumio_pylo/cli/commands/workload_relabeler.py +510 -0
- illumio_pylo/cli/commands/workload_reset_names_to_null.py +83 -0
- illumio_pylo/cli/commands/workload_used_in_rule_finder.py +80 -0
- illumio_pylo/docs/Doxygen +1757 -0
- illumio_pylo/tmp.py +104 -0
- illumio_pylo/utilities/__init__.py +0 -0
- illumio_pylo/utilities/cli.py +10 -0
- illumio_pylo/utilities/credentials.example.json +20 -0
- illumio_pylo/utilities/explorer_report_exporter.py +86 -0
- illumio_pylo/utilities/health_monitoring.py +102 -0
- illumio_pylo/utilities/iplist_analyzer.py +148 -0
- illumio_pylo/utilities/iplists_stats_duplicates_unused_finder.py +75 -0
- illumio_pylo/utilities/resources/iplists-import-example.csv +3 -0
- illumio_pylo/utilities/resources/iplists-import-example.xlsx +0 -0
- illumio_pylo/utilities/resources/workload-exporter-filter-example.csv +3 -0
- illumio_pylo/utilities/resources/workloads-import-example.csv +2 -0
- illumio_pylo/utilities/resources/workloads-import-example.xlsx +0 -0
- illumio_pylo/utilities/ven_compatibility_report_export.py +240 -0
- illumio_pylo/utilities/ven_idle_to_illumination.py +344 -0
- illumio_pylo/utilities/ven_reassign_pce.py +183 -0
- illumio_pylo-0.2.5.dist-info/LICENSE +176 -0
- illumio_pylo-0.2.5.dist-info/METADATA +197 -0
- illumio_pylo-0.2.5.dist-info/RECORD +73 -0
- illumio_pylo-0.2.5.dist-info/WHEEL +5 -0
- illumio_pylo-0.2.5.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1077 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from typing import Optional, List, Dict, Literal, TypeVar, Generic, Union
|
|
3
|
+
from datetime import datetime, timedelta
|
|
4
|
+
|
|
5
|
+
import illumio_pylo as pylo
|
|
6
|
+
from .JsonPayloadTypes import RuleCoverageQueryEntryJsonStructure
|
|
7
|
+
from illumio_pylo.API.APIConnector import APIConnector
|
|
8
|
+
|
|
9
|
+
class ExplorerResult:
|
|
10
|
+
_draft_mode_policy_decision: Optional[Literal['allowed', 'blocked', 'blocked_by_boundary']]
|
|
11
|
+
destination_workload_labels_href: List[str]
|
|
12
|
+
source_workload_labels_href: List[str]
|
|
13
|
+
|
|
14
|
+
def __init__(self, data):
|
|
15
|
+
self.raw_json = data
|
|
16
|
+
self.num_connections = data['num_connections']
|
|
17
|
+
|
|
18
|
+
self.policy_decision_string = data['policy_decision']
|
|
19
|
+
self._draft_mode_policy_decision = None
|
|
20
|
+
|
|
21
|
+
self.source_ip_fqdn: Optional[str] = None
|
|
22
|
+
self.destination_ip_fqdn: Optional[str] = None
|
|
23
|
+
|
|
24
|
+
src = data['src']
|
|
25
|
+
self.source_ip: str = src['ip']
|
|
26
|
+
self._source_iplists = src.get('ip_lists')
|
|
27
|
+
self._source_iplists_href: List[str] = []
|
|
28
|
+
if self._source_iplists is not None:
|
|
29
|
+
for href in self._source_iplists:
|
|
30
|
+
self._source_iplists_href.append(href['href'])
|
|
31
|
+
|
|
32
|
+
self.source_workload_href: Optional[str] = None
|
|
33
|
+
workload_data = src.get('workload')
|
|
34
|
+
if workload_data is not None:
|
|
35
|
+
self.source_workload_href: Optional[str] = workload_data.get('href')
|
|
36
|
+
if self.source_workload_href is None:
|
|
37
|
+
raise pylo.PyloApiUnexpectedSyntax("Explorer API has return a record referring to a Workload with no HREF given:", data)
|
|
38
|
+
|
|
39
|
+
self.source_workload_labels_href: Optional[List[str]] = []
|
|
40
|
+
workload_labels_data = workload_data.get('labels')
|
|
41
|
+
if workload_labels_data is not None:
|
|
42
|
+
for label_data in workload_labels_data:
|
|
43
|
+
self.source_workload_labels_href.append(label_data.get('href'))
|
|
44
|
+
|
|
45
|
+
dst = data['dst']
|
|
46
|
+
self.destination_ip: str = dst['ip']
|
|
47
|
+
self.destination_ip_fqdn = dst.get('fqdn')
|
|
48
|
+
self._destination_iplists = dst.get('ip_lists')
|
|
49
|
+
self._destination_iplists_href: List[str] = []
|
|
50
|
+
if self._destination_iplists is not None:
|
|
51
|
+
for href in self._destination_iplists:
|
|
52
|
+
self._destination_iplists_href.append(href['href'])
|
|
53
|
+
|
|
54
|
+
self.destination_workload_href: Optional[str] = None
|
|
55
|
+
workload_data = dst.get('workload')
|
|
56
|
+
if workload_data is not None:
|
|
57
|
+
self.destination_workload_href = workload_data.get('href')
|
|
58
|
+
if self.destination_workload_href is None:
|
|
59
|
+
raise pylo.PyloApiUnexpectedSyntax("Explorer API has return a record referring to a Workload with no HREF given:", data)
|
|
60
|
+
|
|
61
|
+
self.destination_workload_labels_href: Optional[List[str]] = []
|
|
62
|
+
workload_labels_data = workload_data.get('labels')
|
|
63
|
+
if workload_labels_data is not None:
|
|
64
|
+
for label_data in workload_labels_data:
|
|
65
|
+
self.destination_workload_labels_href.append(label_data.get('href'))
|
|
66
|
+
|
|
67
|
+
service_json = data['service']
|
|
68
|
+
self.service_json = service_json
|
|
69
|
+
|
|
70
|
+
self.service_protocol: int = service_json['proto']
|
|
71
|
+
self.service_port: Optional[int] = service_json.get('port')
|
|
72
|
+
self.process_name: Optional[str] = service_json.get('process_name')
|
|
73
|
+
self.username: Optional[str] = service_json.get('user_name')
|
|
74
|
+
|
|
75
|
+
self.first_detected: str = data['timestamp_range']['first_detected']
|
|
76
|
+
self.last_detected: str = data['timestamp_range']['last_detected']
|
|
77
|
+
|
|
78
|
+
self._cast_type: Optional[str] = data.get('transmission')
|
|
79
|
+
|
|
80
|
+
def service_to_str(self, protocol_first=True):
|
|
81
|
+
if protocol_first:
|
|
82
|
+
if self.service_port is None or self.service_port == 0:
|
|
83
|
+
return 'proto/{}'.format(self.service_protocol)
|
|
84
|
+
|
|
85
|
+
if self.service_protocol == 17:
|
|
86
|
+
return 'udp/{}'.format(self.service_port)
|
|
87
|
+
|
|
88
|
+
if self.service_protocol == 6:
|
|
89
|
+
return 'tcp/{}'.format(self.service_port)
|
|
90
|
+
else:
|
|
91
|
+
if self.service_port is None or self.service_port == 0:
|
|
92
|
+
return '{}/proto'.format(self.service_protocol)
|
|
93
|
+
|
|
94
|
+
if self.service_protocol == 17:
|
|
95
|
+
return '{}/udp'.format(self.service_port)
|
|
96
|
+
|
|
97
|
+
if self.service_protocol == 6:
|
|
98
|
+
return '{}/tcp'.format(self.service_port)
|
|
99
|
+
|
|
100
|
+
def service_to_str_array(self):
|
|
101
|
+
if self.service_port is None or self.service_port == 0:
|
|
102
|
+
return [self.service_protocol, 'proto']
|
|
103
|
+
|
|
104
|
+
if self.service_protocol == 17:
|
|
105
|
+
return [self.service_port, 'udp']
|
|
106
|
+
|
|
107
|
+
if self.service_protocol == 6:
|
|
108
|
+
return [self.service_port, 'tcp']
|
|
109
|
+
|
|
110
|
+
return ['n/a', 'n/a']
|
|
111
|
+
|
|
112
|
+
def source_is_workload(self):
|
|
113
|
+
return self.source_workload_href is not None
|
|
114
|
+
|
|
115
|
+
def destination_is_workload(self):
|
|
116
|
+
return self.destination_workload_href is not None
|
|
117
|
+
|
|
118
|
+
def get_source_workload_href(self):
|
|
119
|
+
return self.source_workload_href
|
|
120
|
+
|
|
121
|
+
def get_destination_workload_href(self):
|
|
122
|
+
return self.destination_workload_href
|
|
123
|
+
|
|
124
|
+
def get_source_workload(self, org_for_resolution: 'pylo.Organization') -> Optional['pylo.Workload']:
|
|
125
|
+
if self.source_workload_href is None:
|
|
126
|
+
return None
|
|
127
|
+
return org_for_resolution.WorkloadStore.find_by_href_or_create_tmp(self.source_workload_href, '*DELETED*')
|
|
128
|
+
|
|
129
|
+
def get_destination_workload(self, org_for_resolution: 'pylo.Organization') -> Optional['pylo.Workload']:
|
|
130
|
+
if self.destination_workload_href is None:
|
|
131
|
+
return None
|
|
132
|
+
return org_for_resolution.WorkloadStore.find_by_href_or_create_tmp(self.destination_workload_href, '*DELETED*')
|
|
133
|
+
|
|
134
|
+
def get_source_labels_href(self) -> Optional[List[str]]:
|
|
135
|
+
if not self.source_is_workload():
|
|
136
|
+
return None
|
|
137
|
+
return self.source_workload_labels_href
|
|
138
|
+
|
|
139
|
+
def get_destination_labels_href(self) -> Optional[List[str]]:
|
|
140
|
+
if not self.destination_is_workload():
|
|
141
|
+
return None
|
|
142
|
+
return self.destination_workload_labels_href
|
|
143
|
+
|
|
144
|
+
def get_source_iplists(self, org_for_resolution: 'pylo.Organization') ->Dict[str, 'pylo.IPList']:
|
|
145
|
+
if self._source_iplists is None:
|
|
146
|
+
return {}
|
|
147
|
+
|
|
148
|
+
result = {}
|
|
149
|
+
|
|
150
|
+
for record in self._source_iplists:
|
|
151
|
+
href = record.get('href')
|
|
152
|
+
if href is None:
|
|
153
|
+
raise pylo.PyloEx('Cannot find HREF for IPList in Explorer result json', record)
|
|
154
|
+
iplist = org_for_resolution.IPListStore.find_by_href(href)
|
|
155
|
+
if iplist is None:
|
|
156
|
+
raise pylo.PyloEx('Cannot find HREF for IPList in Explorer result json', record)
|
|
157
|
+
|
|
158
|
+
result[href] = iplist
|
|
159
|
+
|
|
160
|
+
return result
|
|
161
|
+
|
|
162
|
+
def get_source_iplists_href(self) -> Optional[List[str]]:
|
|
163
|
+
if self.source_is_workload():
|
|
164
|
+
return None
|
|
165
|
+
if self._source_iplists_href is None:
|
|
166
|
+
return []
|
|
167
|
+
return self._source_iplists_href.copy()
|
|
168
|
+
|
|
169
|
+
def get_destination_iplists_href(self) -> Optional[List[str]]:
|
|
170
|
+
if self.destination_is_workload():
|
|
171
|
+
return None
|
|
172
|
+
|
|
173
|
+
if self._destination_iplists_href is None:
|
|
174
|
+
return []
|
|
175
|
+
return self._destination_iplists_href.copy()
|
|
176
|
+
|
|
177
|
+
def get_destination_iplists(self, org_for_resolution: 'pylo.Organization') ->Dict[str, 'pylo.IPList']:
|
|
178
|
+
if self._destination_iplists is None:
|
|
179
|
+
return {}
|
|
180
|
+
|
|
181
|
+
result = {}
|
|
182
|
+
|
|
183
|
+
for record in self._destination_iplists:
|
|
184
|
+
href = record.get('href')
|
|
185
|
+
if href is None:
|
|
186
|
+
raise pylo.PyloEx('Cannot find HREF for IPList in Explorer result json', record)
|
|
187
|
+
iplist = org_for_resolution.IPListStore.find_by_href(href)
|
|
188
|
+
if iplist is None:
|
|
189
|
+
raise pylo.PyloEx('Cannot find HREF for IPList in Explorer result json', record)
|
|
190
|
+
|
|
191
|
+
result[href] = iplist
|
|
192
|
+
|
|
193
|
+
return result
|
|
194
|
+
|
|
195
|
+
def pd_is_potentially_blocked(self):
|
|
196
|
+
return self.policy_decision_string == 'potentially_blocked'
|
|
197
|
+
|
|
198
|
+
def cast_is_broadcast(self):
|
|
199
|
+
return self._cast_type == 'broadcast'
|
|
200
|
+
|
|
201
|
+
def cast_is_multicast(self):
|
|
202
|
+
return self._cast_type == 'multicast'
|
|
203
|
+
|
|
204
|
+
def cast_is_unicast(self):
|
|
205
|
+
return self._cast_type is not None
|
|
206
|
+
|
|
207
|
+
def set_draft_mode_policy_decision(self, decision: Literal['allowed', 'blocked', 'blocked_by_boundary']):
|
|
208
|
+
self._draft_mode_policy_decision = decision
|
|
209
|
+
|
|
210
|
+
def draft_mode_policy_decision_is_blocked(self) -> Optional[bool]:
|
|
211
|
+
"""
|
|
212
|
+
@return: None if draft_mode was not enabled
|
|
213
|
+
"""
|
|
214
|
+
return self._draft_mode_policy_decision is not None and \
|
|
215
|
+
(self._draft_mode_policy_decision == 'blocked' or self._draft_mode_policy_decision == 'blocked_by_boundary')
|
|
216
|
+
|
|
217
|
+
def draft_mode_policy_decision_is_allowed(self) -> Optional[bool]:
|
|
218
|
+
"""
|
|
219
|
+
@return: None if draft_mode was not enabled
|
|
220
|
+
"""
|
|
221
|
+
return self._draft_mode_policy_decision is not None and self._draft_mode_policy_decision == "allowed"
|
|
222
|
+
|
|
223
|
+
def draft_mode_policy_decision_is_unavailable(self) -> Optional[bool]:
|
|
224
|
+
"""
|
|
225
|
+
@return: None if draft_mode was not enabled
|
|
226
|
+
"""
|
|
227
|
+
return self._draft_mode_policy_decision is None
|
|
228
|
+
|
|
229
|
+
def draft_mode_policy_decision_is_not_defined(self) -> Optional[bool]:
|
|
230
|
+
return self._draft_mode_policy_decision is None
|
|
231
|
+
|
|
232
|
+
def draft_mode_policy_decision_to_str(self) -> str:
|
|
233
|
+
if self._draft_mode_policy_decision is None:
|
|
234
|
+
return 'not_available'
|
|
235
|
+
return self._draft_mode_policy_decision
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class ExplorerResultSetV1:
|
|
239
|
+
|
|
240
|
+
owner: 'APIConnector'
|
|
241
|
+
|
|
242
|
+
def __init__(self, data, owner: 'APIConnector', emulated_process_exclusion={}):
|
|
243
|
+
self.owner = owner
|
|
244
|
+
self._raw_results = data
|
|
245
|
+
if len(emulated_process_exclusion) > 0:
|
|
246
|
+
new_data = []
|
|
247
|
+
for record in self._raw_results:
|
|
248
|
+
if 'process_name' in record['service']:
|
|
249
|
+
if record['service']['process_name'] in emulated_process_exclusion:
|
|
250
|
+
continue
|
|
251
|
+
new_data.append(record)
|
|
252
|
+
self._raw_results = new_data
|
|
253
|
+
|
|
254
|
+
self._records: List[ExplorerResult] = []
|
|
255
|
+
self._gen_records()
|
|
256
|
+
|
|
257
|
+
def _gen_records(self):
|
|
258
|
+
for data in self._raw_results:
|
|
259
|
+
try:
|
|
260
|
+
new_record = ExplorerResult(data)
|
|
261
|
+
self._records.append(new_record)
|
|
262
|
+
|
|
263
|
+
except pylo.PyloApiUnexpectedSyntax as error:
|
|
264
|
+
pylo.log.warn(error)
|
|
265
|
+
|
|
266
|
+
def count_records(self):
|
|
267
|
+
return len(self._raw_results)
|
|
268
|
+
|
|
269
|
+
def get_record(self, line: int):
|
|
270
|
+
if line < 0:
|
|
271
|
+
raise pylo.PyloEx('Invalid line #: {}'.format(line))
|
|
272
|
+
if line >= len(self._raw_results):
|
|
273
|
+
raise pylo.PyloEx('Line # doesnt exists, requested #{} while this set contains only {} (starts at 0)'.
|
|
274
|
+
format(line, len(self._raw_results)))
|
|
275
|
+
|
|
276
|
+
return ExplorerResult(self._raw_results[line])
|
|
277
|
+
|
|
278
|
+
def get_all_records(self) -> List[ExplorerResult]:
|
|
279
|
+
return self._records
|
|
280
|
+
|
|
281
|
+
def merge_similar_records_only_process_and_user_differs(self):
|
|
282
|
+
class HashTable:
|
|
283
|
+
def __init__(self):
|
|
284
|
+
self.entries: Dict[str, List[ExplorerResult]] = {}
|
|
285
|
+
|
|
286
|
+
def load(self, records: List[ExplorerResult]):
|
|
287
|
+
for record in records:
|
|
288
|
+
hash = record.source_ip + record.destination_ip + str(record.source_workload_href) + \
|
|
289
|
+
str(record.destination_workload_href) + record.service_to_str() + \
|
|
290
|
+
record.policy_decision_string + record.draft_mode_policy_decision_to_str()
|
|
291
|
+
entry_from_hash = self.entries.get(hash)
|
|
292
|
+
if entry_from_hash is None:
|
|
293
|
+
self.entries[hash] = [record]
|
|
294
|
+
else:
|
|
295
|
+
entry_from_hash.append(record)
|
|
296
|
+
|
|
297
|
+
def results(self) -> List[ExplorerResult]:
|
|
298
|
+
results_list: List[ExplorerResult] = []
|
|
299
|
+
|
|
300
|
+
for hashEntry in self.entries.values():
|
|
301
|
+
if len(hashEntry) == 1:
|
|
302
|
+
results_list.append(hashEntry[0])
|
|
303
|
+
continue
|
|
304
|
+
|
|
305
|
+
record_to_keep = hashEntry.pop()
|
|
306
|
+
merged_users = []
|
|
307
|
+
merged_processes = []
|
|
308
|
+
count_connections = 0
|
|
309
|
+
|
|
310
|
+
last_detected = record_to_keep.last_detected
|
|
311
|
+
first_detected = record_to_keep.first_detected
|
|
312
|
+
|
|
313
|
+
for record in hashEntry:
|
|
314
|
+
if record.username is not None and len(record.username) > 0:
|
|
315
|
+
merged_users.append(record.username)
|
|
316
|
+
if record.process_name is not None and len(record.process_name) > 0:
|
|
317
|
+
merged_processes.append(record.process_name)
|
|
318
|
+
if last_detected < record.last_detected:
|
|
319
|
+
last_detected = record.last_detected
|
|
320
|
+
if first_detected > record.first_detected:
|
|
321
|
+
first_detected = record.first_detected
|
|
322
|
+
|
|
323
|
+
count_connections = count_connections + record.num_connections
|
|
324
|
+
|
|
325
|
+
merged_users = list(set(merged_users))
|
|
326
|
+
merged_processes = list(set(merged_processes))
|
|
327
|
+
|
|
328
|
+
record_to_keep.process_name = merged_processes
|
|
329
|
+
record_to_keep.username = merged_users
|
|
330
|
+
record_to_keep.num_connections = count_connections
|
|
331
|
+
record_to_keep.last_detected = last_detected
|
|
332
|
+
record_to_keep.first_detected = first_detected
|
|
333
|
+
|
|
334
|
+
results_list.append(record_to_keep)
|
|
335
|
+
|
|
336
|
+
return results_list
|
|
337
|
+
|
|
338
|
+
hash_table: HashTable = HashTable()
|
|
339
|
+
hash_table.load(self._records)
|
|
340
|
+
self._records = hash_table.results()
|
|
341
|
+
|
|
342
|
+
def apply_draft_policy_decision_to_all_records(self):
|
|
343
|
+
draft_manager = RuleCoverageQueryManager(self.owner)
|
|
344
|
+
draft_manager.add_query_from_explorer_results(self._records)
|
|
345
|
+
draft_manager.execute()
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
class RuleCoverageQueryManager:
|
|
350
|
+
|
|
351
|
+
class QueryServices:
|
|
352
|
+
def __init__(self):
|
|
353
|
+
self.service_hash_to_index: Dict[str, int] = {}
|
|
354
|
+
self.services_array: List[Dict] = []
|
|
355
|
+
self.service_index_to_log_ids: Dict[int, List[int]] = {}
|
|
356
|
+
self.service_index_policy_coverage: Dict[int, List[str]] = {} # for a service ID (used as key) returns a list of matching rules HREF
|
|
357
|
+
self.service_index_to_boundary_policy_coverage: Dict[int, List[str]] = {} # for a service ID (used as key) returns a list of matching boundary rules HREF
|
|
358
|
+
|
|
359
|
+
def add_service(self, service_record: Dict, log_id: int):
|
|
360
|
+
service_hash = '' + str(service_record.get('proto', 'no_proto')) + '/' + str(service_record.get('port', 'no_port')) + '/' \
|
|
361
|
+
+ '/' + str(service_record.get('process_name', 'no_process_name')) \
|
|
362
|
+
+ '/' + str(service_record.get('windows_service_name', 'no_windows_service_name'))
|
|
363
|
+
# username is not allows in rule_coverage
|
|
364
|
+
|
|
365
|
+
# print(service_hash)
|
|
366
|
+
|
|
367
|
+
if service_hash not in self.service_hash_to_index:
|
|
368
|
+
self.service_hash_to_index[service_hash] = len(self.services_array)
|
|
369
|
+
self.services_array.append(service_record)
|
|
370
|
+
|
|
371
|
+
service_index = self.service_hash_to_index[service_hash]
|
|
372
|
+
if service_index not in self.service_index_to_log_ids:
|
|
373
|
+
self.service_index_to_log_ids[service_index] = []
|
|
374
|
+
|
|
375
|
+
if log_id not in self.service_index_to_log_ids[service_index]:
|
|
376
|
+
self.service_index_to_log_ids[service_index].append(log_id)
|
|
377
|
+
|
|
378
|
+
def get_policy_decision_for_log_id(self, log_id: int) -> Optional[Literal['allowed', 'blocked', 'blocked_by_boundary']]:
|
|
379
|
+
policy_decision = None
|
|
380
|
+
found_boundary_block = False
|
|
381
|
+
|
|
382
|
+
for service_id, list_of_log_ids in self.service_index_to_log_ids.items():
|
|
383
|
+
if log_id in list_of_log_ids:
|
|
384
|
+
policy_decision = 'blocked'
|
|
385
|
+
policy_coverage = self.service_index_policy_coverage[service_id]
|
|
386
|
+
if len(policy_coverage) > 0:
|
|
387
|
+
return 'allowed'
|
|
388
|
+
|
|
389
|
+
boundary_policy_coverage = self.service_index_to_boundary_policy_coverage[service_id]
|
|
390
|
+
if len(boundary_policy_coverage) > 0:
|
|
391
|
+
found_boundary_block = True
|
|
392
|
+
|
|
393
|
+
if found_boundary_block:
|
|
394
|
+
return 'blocked_by_boundary'
|
|
395
|
+
|
|
396
|
+
return policy_decision
|
|
397
|
+
|
|
398
|
+
class ObjectToObjectQuery:
|
|
399
|
+
def __init__(self, src_href: str, src_type: Literal['ip_list','workload'], dst_href: str, dst_type: Literal['ip_list','workload']):
|
|
400
|
+
self.src_href = src_href
|
|
401
|
+
self.dst_href = dst_href
|
|
402
|
+
self.src_type = src_type
|
|
403
|
+
self.dst_type = dst_type
|
|
404
|
+
self.services = RuleCoverageQueryManager.QueryServices()
|
|
405
|
+
|
|
406
|
+
def add_service(self, service_record: Dict, log_id: int):
|
|
407
|
+
self.services.add_service(service_record, log_id)
|
|
408
|
+
|
|
409
|
+
def get_policy_decision_for_log_id(self, log_id: int) -> Optional[Literal['allowed', 'blocked', 'blocked_by_boundary']]:
|
|
410
|
+
return self.services.get_policy_decision_for_log_id(log_id)
|
|
411
|
+
|
|
412
|
+
def process_response(self, rules: Dict[str, str], response: [[str]]):
|
|
413
|
+
if len(response) != len(self.services.services_array):
|
|
414
|
+
raise Exception('Unexpected response from rule coverage query with mis-matching services count vs reply')
|
|
415
|
+
|
|
416
|
+
for index, single_response in enumerate(response):
|
|
417
|
+
rules_array: [Dict] = []
|
|
418
|
+
for rule in single_response:
|
|
419
|
+
rules_array.append(rules[rule])
|
|
420
|
+
self.services.service_index_policy_coverage[index] = rules_array
|
|
421
|
+
|
|
422
|
+
def process_response_boundary_deny(self, rules: Dict[str, str], response: [[str]]):
|
|
423
|
+
if len(response) != len(self.services.services_array):
|
|
424
|
+
raise Exception('Unexpected response from rule coverage query with mis-matching services count vs reply')
|
|
425
|
+
|
|
426
|
+
for index, single_response in enumerate(response):
|
|
427
|
+
rules_array: [Dict] = []
|
|
428
|
+
for rule in single_response:
|
|
429
|
+
rules_array.append(rules[rule])
|
|
430
|
+
self.services.service_index_to_boundary_policy_coverage[index] = rules_array
|
|
431
|
+
|
|
432
|
+
def generate_api_payload(self) -> RuleCoverageQueryEntryJsonStructure:
|
|
433
|
+
payload: RuleCoverageQueryEntryJsonStructure = {"resolve_labels_as": {"source": ["workloads"], "destination": ["workloads"]}, "services": [],
|
|
434
|
+
'source': {self.src_type: {'href': self.src_href}},
|
|
435
|
+
'destination': {self.dst_type: {'href': self.dst_href}}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
for service_id in range(0, len(self.services.services_array)):
|
|
439
|
+
service = self.services.services_array[service_id]
|
|
440
|
+
# print(service)
|
|
441
|
+
service_json: Dict = service.copy()
|
|
442
|
+
service_json['protocol'] = service_json.pop('proto')
|
|
443
|
+
if 'port' in service_json and service_json['protocol'] != 17 and service_json['protocol'] != 6:
|
|
444
|
+
service_json.pop('port')
|
|
445
|
+
if 'user_name' in service_json:
|
|
446
|
+
service_json.pop('user_name')
|
|
447
|
+
payload['services'].append(service_json)
|
|
448
|
+
|
|
449
|
+
return payload
|
|
450
|
+
|
|
451
|
+
class QueryManager:
|
|
452
|
+
def __init__(self, src_type:Literal['ip_list','workload'], dst_type:Literal['ip_list','workload'] ,include_boundary_rules: bool = True):
|
|
453
|
+
self.queries: Dict[str, RuleCoverageQueryManager.ObjectToObjectQuery] = {}
|
|
454
|
+
self.include_boundary_rules = include_boundary_rules
|
|
455
|
+
self.src_type = src_type
|
|
456
|
+
self.dst_type = dst_type
|
|
457
|
+
|
|
458
|
+
def execute(self, connector: APIConnector, queries_per_batch: int):
|
|
459
|
+
# split queries into arrays of size queries_per_batch
|
|
460
|
+
query_batches: List[List[RuleCoverageQueryManager.ObjectToObjectQuery]] = []
|
|
461
|
+
query_batch: List[RuleCoverageQueryManager.ObjectToObjectQuery] = []
|
|
462
|
+
for query in self.queries.values():
|
|
463
|
+
query_batch.append(query)
|
|
464
|
+
if len(query_batch) == queries_per_batch:
|
|
465
|
+
query_batches.append(query_batch)
|
|
466
|
+
query_batch = []
|
|
467
|
+
if len(query_batch) > 0:
|
|
468
|
+
query_batches.append(query_batch)
|
|
469
|
+
|
|
470
|
+
# print(f'{len(query_batches)} batches of {queries_per_batch} queries')
|
|
471
|
+
|
|
472
|
+
for query_batch in query_batches:
|
|
473
|
+
# print(f'Executing batch of {len(query_batch)} queries')
|
|
474
|
+
payload = []
|
|
475
|
+
for query in query_batch:
|
|
476
|
+
payload.append(query.generate_api_payload())
|
|
477
|
+
|
|
478
|
+
api_response = connector.rule_coverage_query(payload, include_boundary_rules=self.include_boundary_rules)
|
|
479
|
+
# print(api_response)
|
|
480
|
+
# print('-------------------------------------------------------')
|
|
481
|
+
|
|
482
|
+
edges = api_response.get('edges')
|
|
483
|
+
if edges is None:
|
|
484
|
+
raise pylo.PyloEx('rule_coverage request has returned no "edges"', api_response)
|
|
485
|
+
|
|
486
|
+
rules = api_response.get('rules')
|
|
487
|
+
if rules is None:
|
|
488
|
+
raise pylo.PyloEx('rule_coverage request has returned no "rules"', api_response)
|
|
489
|
+
|
|
490
|
+
if len(edges) != len(query_batch):
|
|
491
|
+
raise pylo.PyloEx("rule_coverage has returned {} records while {} where requested".format(len(edges), len(query_batch)))
|
|
492
|
+
|
|
493
|
+
for response_index, edge in enumerate(edges):
|
|
494
|
+
query = query_batch[response_index]
|
|
495
|
+
# print(f'Processing edge {edge} against query {query.ip_list_href} -> {query.workload_href} -> {len(query.services.services_array)}')
|
|
496
|
+
query.process_response(rules, edge)
|
|
497
|
+
|
|
498
|
+
if self.include_boundary_rules:
|
|
499
|
+
deny_edges = api_response.get('deny_edges')
|
|
500
|
+
if deny_edges is None:
|
|
501
|
+
raise pylo.PyloEx('rule_coverage request has returned no "deny_edges"', api_response)
|
|
502
|
+
if len(deny_edges) != len(query_batch):
|
|
503
|
+
raise pylo.PyloEx("rule_coverage has returned {} deny_edges while {} where requested".format(len(deny_edges), len(query_batch)))
|
|
504
|
+
|
|
505
|
+
deny_rules = api_response.get('deny_rules')
|
|
506
|
+
if deny_rules is None:
|
|
507
|
+
raise pylo.PyloEx('rule_coverage request has returned no "deny_rules"', api_response)
|
|
508
|
+
|
|
509
|
+
for response_index, edge in enumerate(deny_edges):
|
|
510
|
+
query = query_batch[response_index]
|
|
511
|
+
# print(f'Processing deny_edge {edge} against query {query.src_workload_href} -> {query.dst_workload_href} -> {len(query.services.services_array)}')
|
|
512
|
+
query.process_response_boundary_deny(deny_rules, edge)
|
|
513
|
+
|
|
514
|
+
def get_policy_decision_for_log_id(self, log_id: int) -> Optional[Literal["allowed", "blocked", "blocked_by_boundary"]]:
|
|
515
|
+
policy_decision: Optional[Literal["allowed", "blocked", "blocked_by_boundary"]] = None
|
|
516
|
+
found_blocked_by_boundary = False
|
|
517
|
+
|
|
518
|
+
for query in self.queries.values():
|
|
519
|
+
policy_decision = query.get_policy_decision_for_log_id(log_id) or policy_decision
|
|
520
|
+
if policy_decision == 'allowed':
|
|
521
|
+
return policy_decision
|
|
522
|
+
if query.get_policy_decision_for_log_id(log_id) == 'blocked_by_boundary':
|
|
523
|
+
found_blocked_by_boundary = True
|
|
524
|
+
|
|
525
|
+
if found_blocked_by_boundary:
|
|
526
|
+
return 'blocked_by_boundary'
|
|
527
|
+
|
|
528
|
+
return policy_decision
|
|
529
|
+
|
|
530
|
+
def add_query(self, log_id: int, src_href: str, dst_href: str, service_record):
|
|
531
|
+
hash_key = src_href + dst_href
|
|
532
|
+
if hash_key not in self.queries:
|
|
533
|
+
self.queries[hash_key] = RuleCoverageQueryManager.ObjectToObjectQuery(src_href, self.src_type, dst_href, self.dst_type)
|
|
534
|
+
|
|
535
|
+
self.queries[hash_key].add_service(service_record, log_id)
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
def __init__(self, owner: APIConnector):
|
|
539
|
+
self.owner = owner
|
|
540
|
+
self.iplist_to_workload_query_manager = RuleCoverageQueryManager.QueryManager('ip_list', 'workload')
|
|
541
|
+
self.workload_to_iplist_query_manager = RuleCoverageQueryManager.QueryManager('workload', 'ip_list')
|
|
542
|
+
self.workload_to_workload_query_manager = RuleCoverageQueryManager.QueryManager('workload', 'workload')
|
|
543
|
+
self.log_id = 0
|
|
544
|
+
self.log_to_id: Dict[ExplorerResult, int] = {}
|
|
545
|
+
self.count_invalid_records = 0
|
|
546
|
+
self.any_iplist_href = self.owner.objects_iplists_get_default_any()
|
|
547
|
+
if self.any_iplist_href is None:
|
|
548
|
+
raise pylo.PyloEx('No "any" iplist found')
|
|
549
|
+
|
|
550
|
+
def add_query_from_explorer_results(self, explorer_results: List[ExplorerResult]) -> None:
|
|
551
|
+
for explorer_result in explorer_results:
|
|
552
|
+
self.add_query_from_explorer_result(explorer_result)
|
|
553
|
+
|
|
554
|
+
def add_query_from_explorer_result(self, log: ExplorerResult):
|
|
555
|
+
self.log_id += 1
|
|
556
|
+
self.log_to_id[log] = self.log_id
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
if not log.source_is_workload():
|
|
560
|
+
if log.destination_is_workload():
|
|
561
|
+
iplist_hrefs = log.get_source_iplists_href()
|
|
562
|
+
if iplist_hrefs is None:
|
|
563
|
+
iplist_hrefs = [self.any_iplist_href]
|
|
564
|
+
else:
|
|
565
|
+
iplist_hrefs.append(self.any_iplist_href)
|
|
566
|
+
|
|
567
|
+
for iplist_href in iplist_hrefs:
|
|
568
|
+
self.iplist_to_workload_query_manager.add_query(log_id=self.log_id,
|
|
569
|
+
src_href=iplist_href,
|
|
570
|
+
dst_href=log.get_destination_workload_href(),
|
|
571
|
+
service_record=log.service_json)
|
|
572
|
+
else: # IPList to IPList should never happen!
|
|
573
|
+
self.count_invalid_records += 1
|
|
574
|
+
pass
|
|
575
|
+
else:
|
|
576
|
+
if not log.destination_is_workload():
|
|
577
|
+
iplist_hrefs = log.get_destination_iplists_href()
|
|
578
|
+
if iplist_hrefs is None:
|
|
579
|
+
iplist_hrefs = [self.any_iplist_href]
|
|
580
|
+
else:
|
|
581
|
+
iplist_hrefs.append(self.any_iplist_href)
|
|
582
|
+
|
|
583
|
+
for iplist_href in iplist_hrefs:
|
|
584
|
+
self.workload_to_iplist_query_manager.add_query(log_id=self.log_id,
|
|
585
|
+
src_href=log.get_source_workload_href(),
|
|
586
|
+
dst_href=iplist_href,
|
|
587
|
+
service_record=log.service_json)
|
|
588
|
+
else:
|
|
589
|
+
self.workload_to_workload_query_manager.add_query(log_id=self.log_id,
|
|
590
|
+
src_href=log.get_source_workload_href(),
|
|
591
|
+
dst_href=log.get_destination_workload_href(),
|
|
592
|
+
service_record=log.service_json)
|
|
593
|
+
|
|
594
|
+
def count_queries(self):
|
|
595
|
+
return len(self.iplist_to_workload_query_manager.queries)\
|
|
596
|
+
+ len(self.workload_to_iplist_query_manager.queries)\
|
|
597
|
+
+ len(self.workload_to_workload_query_manager.queries)
|
|
598
|
+
|
|
599
|
+
def count_real_queries(self):
|
|
600
|
+
_log_ids = {}
|
|
601
|
+
for query in self.iplist_to_workload_query_manager.queries.values():
|
|
602
|
+
for log_ids in query.services.service_index_to_log_ids.values():
|
|
603
|
+
for log_id in log_ids:
|
|
604
|
+
_log_ids[log_id] = True
|
|
605
|
+
|
|
606
|
+
for query in self.workload_to_iplist_query_manager.queries.values():
|
|
607
|
+
for log_ids in query.services.service_index_to_log_ids.values():
|
|
608
|
+
for log_id in log_ids:
|
|
609
|
+
_log_ids[log_id] = True
|
|
610
|
+
|
|
611
|
+
for query in self.workload_to_workload_query_manager.queries.values():
|
|
612
|
+
for log_ids in query.services.service_index_to_log_ids.values():
|
|
613
|
+
for log_id in log_ids:
|
|
614
|
+
_log_ids[log_id] = True
|
|
615
|
+
|
|
616
|
+
return len(_log_ids)
|
|
617
|
+
|
|
618
|
+
def _get_policy_decision_for_log_id(self, log_id: int) -> Optional[Literal['allowed', 'blocked', 'blocked_by_boundary']]:
|
|
619
|
+
decision = self.iplist_to_workload_query_manager.get_policy_decision_for_log_id(log_id)
|
|
620
|
+
if decision == 'allowed':
|
|
621
|
+
return decision
|
|
622
|
+
|
|
623
|
+
newDecision = self.workload_to_iplist_query_manager.get_policy_decision_for_log_id(log_id)
|
|
624
|
+
decision = newDecision or decision
|
|
625
|
+
if decision == 'allowed':
|
|
626
|
+
return decision
|
|
627
|
+
|
|
628
|
+
newDecision = self.workload_to_workload_query_manager.get_policy_decision_for_log_id(log_id) or decision
|
|
629
|
+
decision = newDecision or decision
|
|
630
|
+
if decision == 'allowed':
|
|
631
|
+
return decision
|
|
632
|
+
|
|
633
|
+
return decision
|
|
634
|
+
|
|
635
|
+
def apply_policy_decisions_to_logs(self):
|
|
636
|
+
for log, log_id in self.log_to_id.items():
|
|
637
|
+
decision = self._get_policy_decision_for_log_id(log_id)
|
|
638
|
+
if decision is None:
|
|
639
|
+
# if len(log.get_source_iplists_href()) == 0 and len(log.get_destination_iplists_href()) == 0 is None:
|
|
640
|
+
# # happens when source or destination is part of no IPList
|
|
641
|
+
# decision = 'blocked'
|
|
642
|
+
# else:
|
|
643
|
+
pylo.log.error(pylo.nice_json(log.raw_json))
|
|
644
|
+
raise pylo.PyloEx('No decision found for log_id {}'.format(log_id), log.raw_json)
|
|
645
|
+
|
|
646
|
+
log.set_draft_mode_policy_decision(decision)
|
|
647
|
+
|
|
648
|
+
def execute(self):
|
|
649
|
+
queries_per_batch = 100
|
|
650
|
+
self.iplist_to_workload_query_manager.execute(self.owner, queries_per_batch)
|
|
651
|
+
self.workload_to_iplist_query_manager.execute(self.owner, queries_per_batch)
|
|
652
|
+
self.workload_to_workload_query_manager.execute(self.owner, queries_per_batch)
|
|
653
|
+
|
|
654
|
+
self.apply_policy_decisions_to_logs()
|
|
655
|
+
|
|
656
|
+
class ExplorerFilterSetV1:
|
|
657
|
+
exclude_processes_emulate: Dict[str, str]
|
|
658
|
+
_exclude_processes: List[str]
|
|
659
|
+
_exclude_direct_services: List['pylo.DirectServiceInRule']
|
|
660
|
+
_time_from: Optional[datetime]
|
|
661
|
+
_time_to: Optional[datetime]
|
|
662
|
+
_policy_decision_filter: List[str]
|
|
663
|
+
_consumer_labels: Dict[str, Union['pylo.Label', 'pylo.LabelGroup']]
|
|
664
|
+
__filter_provider_ip_exclude: List[str]
|
|
665
|
+
__filter_consumer_ip_exclude: List[str]
|
|
666
|
+
__filter_provider_ip_include: List[str]
|
|
667
|
+
__filter_consumer_ip_include: List[str]
|
|
668
|
+
|
|
669
|
+
def __init__(self, max_results=10000):
|
|
670
|
+
self.__filter_consumer_ip_exclude = []
|
|
671
|
+
self.__filter_provider_ip_exclude = []
|
|
672
|
+
self.__filter_consumer_ip_include = []
|
|
673
|
+
self.__filter_provider_ip_include = []
|
|
674
|
+
self._consumer_labels: Dict[str, Union[pylo.Label, pylo.LabelGroup]] = {}
|
|
675
|
+
self._consumer_exclude_labels: Dict[str, Union[pylo.Label, pylo.LabelGroup]] = {}
|
|
676
|
+
self._provider_labels: Dict[str, Union[pylo.Label, pylo.LabelGroup]] = {}
|
|
677
|
+
self._provider_exclude_labels: Dict[str, Union[pylo.Label, pylo.LabelGroup]] = {}
|
|
678
|
+
|
|
679
|
+
self._consumer_workloads = {}
|
|
680
|
+
self._provider_workloads = {}
|
|
681
|
+
|
|
682
|
+
self._consumer_iplists = {}
|
|
683
|
+
self._consumer_iplists_exclude = {}
|
|
684
|
+
self._provider_iplists = {}
|
|
685
|
+
self._provider_iplists_exclude = {}
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
self.max_results = max_results
|
|
689
|
+
self._policy_decision_filter = []
|
|
690
|
+
self._time_from = None
|
|
691
|
+
self._time_to = None
|
|
692
|
+
|
|
693
|
+
self._include_direct_services = []
|
|
694
|
+
|
|
695
|
+
self._exclude_broadcast = False
|
|
696
|
+
self._exclude_multicast = False
|
|
697
|
+
self._exclude_direct_services = []
|
|
698
|
+
self.exclude_processes_emulate = {}
|
|
699
|
+
self._exclude_processes = []
|
|
700
|
+
|
|
701
|
+
@staticmethod
|
|
702
|
+
def __filter_prop_add_label(prop_dict, label_or_href):
|
|
703
|
+
"""
|
|
704
|
+
|
|
705
|
+
@type prop_dict: dict
|
|
706
|
+
@type label_or_href: str|pylo.Label|pylo.LabelGroup
|
|
707
|
+
"""
|
|
708
|
+
if isinstance(label_or_href, str):
|
|
709
|
+
prop_dict[label_or_href] = label_or_href
|
|
710
|
+
return
|
|
711
|
+
elif isinstance(label_or_href, pylo.Label):
|
|
712
|
+
prop_dict[label_or_href.href] = label_or_href
|
|
713
|
+
return
|
|
714
|
+
elif isinstance(label_or_href, pylo.LabelGroup):
|
|
715
|
+
# since 21.5 labelgroups can be included directly
|
|
716
|
+
# for nested_label in label_or_href.expand_nested_to_array():
|
|
717
|
+
# prop_dict[nested_label.href] = nested_label
|
|
718
|
+
prop_dict[label_or_href.href] = label_or_href
|
|
719
|
+
return
|
|
720
|
+
else:
|
|
721
|
+
raise pylo.PyloEx("Unsupported object type {}".format(type(label_or_href)))
|
|
722
|
+
|
|
723
|
+
def consumer_include_label(self, label_or_href):
|
|
724
|
+
"""
|
|
725
|
+
|
|
726
|
+
@type label_or_href: str|pylo.Label|pylo.LabelGroup
|
|
727
|
+
"""
|
|
728
|
+
self.__filter_prop_add_label(self._consumer_labels, label_or_href)
|
|
729
|
+
|
|
730
|
+
def consumer_exclude_label(self, label_or_href: Union[str, 'pylo.Label', 'pylo.LabelGroup']):
|
|
731
|
+
self.__filter_prop_add_label(self._consumer_exclude_labels, label_or_href)
|
|
732
|
+
|
|
733
|
+
def consumer_exclude_labels(self, labels: List[Union[str, 'pylo.Label', 'pylo.LabelGroup']]):
|
|
734
|
+
for label in labels:
|
|
735
|
+
self.consumer_exclude_label(label)
|
|
736
|
+
|
|
737
|
+
def consumer_include_workload(self, workload_or_href:Union[str, 'pylo.Workload']):
|
|
738
|
+
if isinstance(workload_or_href, str):
|
|
739
|
+
self._consumer_workloads[workload_or_href] = workload_or_href
|
|
740
|
+
return
|
|
741
|
+
|
|
742
|
+
if isinstance(workload_or_href, pylo.Workload):
|
|
743
|
+
self._consumer_workloads[workload_or_href.href] = workload_or_href.href
|
|
744
|
+
return
|
|
745
|
+
|
|
746
|
+
raise pylo.PyloEx("Unsupported object type {}".format(type(workload_or_href)))
|
|
747
|
+
|
|
748
|
+
def provider_include_workload(self, workload_or_href:Union[str, 'pylo.Workload']):
|
|
749
|
+
if isinstance(workload_or_href, str):
|
|
750
|
+
self._provider_workloads[workload_or_href] = workload_or_href
|
|
751
|
+
return
|
|
752
|
+
|
|
753
|
+
if isinstance(workload_or_href, pylo.Workload):
|
|
754
|
+
self._provider_workloads[workload_or_href.href] = workload_or_href.href
|
|
755
|
+
return
|
|
756
|
+
|
|
757
|
+
raise pylo.PyloEx("Unsupported object type {}".format(type(workload_or_href)))
|
|
758
|
+
|
|
759
|
+
def consumer_include_iplist(self, iplist_or_href: Union[str, 'pylo.IPList']):
|
|
760
|
+
if isinstance(iplist_or_href, str):
|
|
761
|
+
self._consumer_iplists[iplist_or_href] = iplist_or_href
|
|
762
|
+
return
|
|
763
|
+
|
|
764
|
+
if isinstance(iplist_or_href, pylo.IPList):
|
|
765
|
+
self._consumer_iplists[iplist_or_href.href] = iplist_or_href.href
|
|
766
|
+
return
|
|
767
|
+
|
|
768
|
+
raise pylo.PyloEx("Unsupported object type {}".format(type(iplist_or_href)))
|
|
769
|
+
|
|
770
|
+
def consumer_exclude_cidr(self, ipaddress: str):
|
|
771
|
+
self.__filter_consumer_ip_exclude.append(ipaddress)
|
|
772
|
+
|
|
773
|
+
def consumer_exclude_iplist(self, iplist_or_href: Union[str, 'pylo.IPList']):
|
|
774
|
+
if isinstance(iplist_or_href, str):
|
|
775
|
+
self._consumer_iplists_exclude[iplist_or_href] = iplist_or_href
|
|
776
|
+
return
|
|
777
|
+
|
|
778
|
+
if isinstance(iplist_or_href, pylo.IPList):
|
|
779
|
+
self._consumer_iplists_exclude[iplist_or_href.href] = iplist_or_href.href
|
|
780
|
+
return
|
|
781
|
+
|
|
782
|
+
raise pylo.PyloEx("Unsupported object type {}".format(type(iplist_or_href)))
|
|
783
|
+
|
|
784
|
+
def consumer_exclude_ip4map(self, map: 'pylo.IP4Map'):
|
|
785
|
+
for item in map.to_list_of_cidr_string():
|
|
786
|
+
self.consumer_exclude_cidr(item)
|
|
787
|
+
|
|
788
|
+
def consumer_include_cidr(self, ipaddress: str):
|
|
789
|
+
self.__filter_consumer_ip_include.append(ipaddress)
|
|
790
|
+
|
|
791
|
+
def consumer_include_ip4map(self, map: 'pylo.IP4Map'):
|
|
792
|
+
for item in map.to_list_of_cidr_string(skip_netmask_for_32=True):
|
|
793
|
+
self.consumer_include_cidr(item)
|
|
794
|
+
|
|
795
|
+
def provider_include_label(self, label_or_href):
|
|
796
|
+
"""
|
|
797
|
+
|
|
798
|
+
@type label_or_href: str|pylo.Label|pylo.LabelGroup
|
|
799
|
+
"""
|
|
800
|
+
self.__filter_prop_add_label(self._provider_labels, label_or_href)
|
|
801
|
+
|
|
802
|
+
def provider_include_iplist(self, iplist_or_href: Union[str, 'pylo.IPList']):
|
|
803
|
+
if isinstance(iplist_or_href, str):
|
|
804
|
+
self._provider_iplists[iplist_or_href] = iplist_or_href
|
|
805
|
+
return
|
|
806
|
+
|
|
807
|
+
if isinstance(iplist_or_href, pylo.IPList):
|
|
808
|
+
self._provider_iplists[iplist_or_href.href] = iplist_or_href.href
|
|
809
|
+
return
|
|
810
|
+
|
|
811
|
+
raise pylo.PyloEx("Unsupported object type {}".format(type(iplist_or_href)))
|
|
812
|
+
|
|
813
|
+
def provider_exclude_label(self, label_or_href: Union[str, 'pylo.Label', 'pylo.LabelGroup']):
|
|
814
|
+
self.__filter_prop_add_label(self._provider_exclude_labels, label_or_href)
|
|
815
|
+
|
|
816
|
+
def provider_exclude_labels(self, labels_or_hrefs: List[Union[str, 'pylo.Label', 'pylo.LabelGroup']]):
|
|
817
|
+
for label in labels_or_hrefs:
|
|
818
|
+
self.provider_exclude_label(label)
|
|
819
|
+
|
|
820
|
+
def provider_exclude_cidr(self, ipaddress: str):
|
|
821
|
+
self.__filter_provider_ip_exclude.append(ipaddress)
|
|
822
|
+
|
|
823
|
+
def provider_exclude_iplist(self, iplist_or_href: Union[str, 'pylo.IPList']):
|
|
824
|
+
if isinstance(iplist_or_href, str):
|
|
825
|
+
self._provider_iplists_exclude[iplist_or_href] = iplist_or_href
|
|
826
|
+
return
|
|
827
|
+
|
|
828
|
+
if isinstance(iplist_or_href, pylo.IPList):
|
|
829
|
+
self._provider_iplists_exclude[iplist_or_href.href] = iplist_or_href.href
|
|
830
|
+
return
|
|
831
|
+
|
|
832
|
+
raise pylo.PyloEx("Unsupported object type {}".format(type(iplist_or_href)))
|
|
833
|
+
|
|
834
|
+
def provider_exclude_ip4map(self, map: 'pylo.IP4Map'):
|
|
835
|
+
for item in map.to_list_of_cidr_string(skip_netmask_for_32=True):
|
|
836
|
+
self.provider_exclude_cidr(item)
|
|
837
|
+
|
|
838
|
+
def provider_include_cidr(self, ipaddress: str):
|
|
839
|
+
self.__filter_provider_ip_include.append(ipaddress)
|
|
840
|
+
|
|
841
|
+
def provider_include_ip4map(self, map: 'pylo.IP4Map'):
|
|
842
|
+
for item in map.to_list_of_cidr_string():
|
|
843
|
+
self.provider_include_cidr(item)
|
|
844
|
+
|
|
845
|
+
def service_include_add(self, service: Union['pylo.DirectServiceInRule',str]):
|
|
846
|
+
if isinstance(service, str):
|
|
847
|
+
self._include_direct_services.append(pylo.DirectServiceInRule.create_from_text(service))
|
|
848
|
+
return
|
|
849
|
+
self._include_direct_services.append(service)
|
|
850
|
+
|
|
851
|
+
def service_include_add_protocol(self, protocol: int):
|
|
852
|
+
self._include_direct_services.append(pylo.DirectServiceInRule(proto=protocol))
|
|
853
|
+
|
|
854
|
+
def service_include_add_protocol_tcp(self):
|
|
855
|
+
self._include_direct_services.append(pylo.DirectServiceInRule(proto=6))
|
|
856
|
+
|
|
857
|
+
def service_include_add_protocol_udp(self):
|
|
858
|
+
self._include_direct_services.append(pylo.DirectServiceInRule(proto=17))
|
|
859
|
+
|
|
860
|
+
def service_exclude_add(self, service: 'pylo.DirectServiceInRule'):
|
|
861
|
+
self._exclude_direct_services.append(service)
|
|
862
|
+
|
|
863
|
+
def service_exclude_add_protocol(self, protocol: int):
|
|
864
|
+
self._exclude_direct_services.append(pylo.DirectServiceInRule(proto=protocol))
|
|
865
|
+
|
|
866
|
+
def service_exclude_add_protocol_tcp(self):
|
|
867
|
+
self._exclude_direct_services.append(pylo.DirectServiceInRule(proto=6))
|
|
868
|
+
|
|
869
|
+
def service_exclude_add_protocol_udp(self):
|
|
870
|
+
self._exclude_direct_services.append(pylo.DirectServiceInRule(proto=17))
|
|
871
|
+
|
|
872
|
+
def process_exclude_add(self, process_name: str, emulate_on_client=False):
|
|
873
|
+
if emulate_on_client:
|
|
874
|
+
self.exclude_processes_emulate[process_name] = process_name
|
|
875
|
+
else:
|
|
876
|
+
self._exclude_processes.append(process_name)
|
|
877
|
+
|
|
878
|
+
def set_exclude_broadcast(self, exclude=True):
|
|
879
|
+
self._exclude_broadcast = exclude
|
|
880
|
+
|
|
881
|
+
def set_exclude_multicast(self, exclude=True):
|
|
882
|
+
self._exclude_multicast = exclude
|
|
883
|
+
|
|
884
|
+
def set_time_from(self, time: datetime):
|
|
885
|
+
self._time_from = time
|
|
886
|
+
|
|
887
|
+
def set_time_from_x_seconds_ago(self, seconds: int):
|
|
888
|
+
self._time_from = datetime.utcnow() - timedelta(seconds=seconds)
|
|
889
|
+
|
|
890
|
+
def set_time_from_x_days_ago(self, days: int):
|
|
891
|
+
return self.set_time_from_x_seconds_ago(days*60*60*24)
|
|
892
|
+
|
|
893
|
+
def set_max_results(self, max: int):
|
|
894
|
+
self.max_results = max
|
|
895
|
+
|
|
896
|
+
def set_time_to(self, time: datetime):
|
|
897
|
+
self._time_to = time
|
|
898
|
+
|
|
899
|
+
def set_time_to_x_seconds_ago(self, seconds: int):
|
|
900
|
+
self._time_to = datetime.utcnow() - timedelta(seconds=seconds)
|
|
901
|
+
|
|
902
|
+
def set_time_to_x_days_ago(self, days: int):
|
|
903
|
+
return self.set_time_to_x_seconds_ago(days*60*60*24)
|
|
904
|
+
|
|
905
|
+
def filter_on_policy_decision_unknown(self):
|
|
906
|
+
self._policy_decision_filter.append('unknown')
|
|
907
|
+
|
|
908
|
+
def filter_on_policy_decision_blocked(self):
|
|
909
|
+
self._policy_decision_filter.append('blocked')
|
|
910
|
+
|
|
911
|
+
def filter_on_policy_decision_potentially_blocked(self):
|
|
912
|
+
self._policy_decision_filter.append('potentially_blocked')
|
|
913
|
+
|
|
914
|
+
def filter_on_policy_decision_all_blocked(self):
|
|
915
|
+
self.filter_on_policy_decision_blocked()
|
|
916
|
+
self.filter_on_policy_decision_potentially_blocked()
|
|
917
|
+
|
|
918
|
+
def filter_on_policy_decision_allowed(self):
|
|
919
|
+
self._policy_decision_filter.append('allowed')
|
|
920
|
+
|
|
921
|
+
def generate_json_query(self):
|
|
922
|
+
# examples:
|
|
923
|
+
# {"sources":{"include":[[]],"exclude":[]}
|
|
924
|
+
# "destinations":{"include":[[]],"exclude":[]},
|
|
925
|
+
# "services":{"include":[],"exclude":[]},
|
|
926
|
+
# "sources_destinations_query_op":"and",
|
|
927
|
+
# "start_date":"2015-02-21T09:18:46.751Z","end_date":"2020-02-21T09:18:46.751Z",
|
|
928
|
+
# "policy_decisions":[],
|
|
929
|
+
# "max_results":10000}
|
|
930
|
+
#
|
|
931
|
+
filters = {
|
|
932
|
+
"sources": {"include": [], "exclude": []},
|
|
933
|
+
"destinations": {"include": [], "exclude": []},
|
|
934
|
+
"services": {"include": [], "exclude": []},
|
|
935
|
+
"sources_destinations_query_op": "and",
|
|
936
|
+
"policy_decisions": self._policy_decision_filter,
|
|
937
|
+
"max_results": self.max_results,
|
|
938
|
+
"query_name": "api call"
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
if self._exclude_broadcast:
|
|
942
|
+
filters['destinations']['exclude'].append({'transmission': 'broadcast'})
|
|
943
|
+
|
|
944
|
+
if self._exclude_multicast:
|
|
945
|
+
filters['destinations']['exclude'].append({'transmission': 'multicast'})
|
|
946
|
+
|
|
947
|
+
if self._time_from is not None:
|
|
948
|
+
filters["start_date"] = self._time_from.strftime('%Y-%m-%dT%H:%M:%SZ')
|
|
949
|
+
else:
|
|
950
|
+
filters["start_date"] = "2010-10-13T11:27:28.824Z",
|
|
951
|
+
|
|
952
|
+
if self._time_to is not None:
|
|
953
|
+
filters["end_date"] = self._time_to.strftime('%Y-%m-%dT%H:%M:%SZ')
|
|
954
|
+
else:
|
|
955
|
+
filters["end_date"] = datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ')
|
|
956
|
+
|
|
957
|
+
if len(self._consumer_labels) > 0:
|
|
958
|
+
tmp = []
|
|
959
|
+
for label in self._consumer_labels.values():
|
|
960
|
+
if label.is_label():
|
|
961
|
+
tmp.append({'label': {'href': label.href}})
|
|
962
|
+
else:
|
|
963
|
+
tmp.append({'label_group': {'href': label.href}})
|
|
964
|
+
filters['sources']['include'].append(tmp)
|
|
965
|
+
|
|
966
|
+
if len(self._consumer_workloads) > 0:
|
|
967
|
+
tmp = []
|
|
968
|
+
for workload_href in self._consumer_workloads.keys():
|
|
969
|
+
tmp.append({'workload': {'href': workload_href}})
|
|
970
|
+
filters['sources']['include'].append(tmp)
|
|
971
|
+
|
|
972
|
+
if len(self._consumer_iplists) > 0:
|
|
973
|
+
tmp = []
|
|
974
|
+
for iplist_href in self._consumer_iplists.keys():
|
|
975
|
+
tmp.append({'ip_list': {'href': iplist_href}})
|
|
976
|
+
filters['sources']['include'].append(tmp)
|
|
977
|
+
|
|
978
|
+
if len(self.__filter_consumer_ip_include) > 0:
|
|
979
|
+
tmp = []
|
|
980
|
+
for ip_txt in self.__filter_consumer_ip_include:
|
|
981
|
+
tmp.append({'ip_address': ip_txt})
|
|
982
|
+
filters['sources']['include'].append(tmp)
|
|
983
|
+
|
|
984
|
+
if len(self._provider_labels) > 0:
|
|
985
|
+
tmp = []
|
|
986
|
+
for label in self._provider_labels.values():
|
|
987
|
+
if label.is_label():
|
|
988
|
+
tmp.append({'label': {'href': label.href}})
|
|
989
|
+
else:
|
|
990
|
+
pass
|
|
991
|
+
tmp.append({'label_group': {'href': label.href}})
|
|
992
|
+
filters['destinations']['include'].append(tmp)
|
|
993
|
+
|
|
994
|
+
if len(self._provider_workloads) > 0:
|
|
995
|
+
tmp = []
|
|
996
|
+
for workload_href in self._provider_workloads.keys():
|
|
997
|
+
tmp.append({'workload': {'href': workload_href}})
|
|
998
|
+
filters['destinations']['include'].append(tmp)
|
|
999
|
+
|
|
1000
|
+
if len(self._provider_iplists) > 0:
|
|
1001
|
+
tmp = []
|
|
1002
|
+
for iplist_href in self._provider_iplists.keys():
|
|
1003
|
+
tmp.append({'ip_list': {'href': iplist_href}})
|
|
1004
|
+
filters['destinations']['include'].append(tmp)
|
|
1005
|
+
|
|
1006
|
+
if len(self.__filter_provider_ip_include) > 0:
|
|
1007
|
+
tmp = []
|
|
1008
|
+
for ip_txt in self.__filter_provider_ip_include:
|
|
1009
|
+
tmp.append({'ip_address': ip_txt})
|
|
1010
|
+
filters['destinations']['include'].append(tmp)
|
|
1011
|
+
|
|
1012
|
+
consumer_exclude_json = []
|
|
1013
|
+
if len(self._consumer_exclude_labels) > 0:
|
|
1014
|
+
for label_href in self._consumer_exclude_labels.keys():
|
|
1015
|
+
filters['sources']['exclude'].append({'label': {'href': label_href}})
|
|
1016
|
+
|
|
1017
|
+
if len(self._consumer_iplists_exclude) > 0:
|
|
1018
|
+
for iplist_href in self._consumer_iplists_exclude.keys():
|
|
1019
|
+
filters['sources']['exclude'].append({'ip_list': {'href': iplist_href}})
|
|
1020
|
+
|
|
1021
|
+
if len(self.__filter_consumer_ip_exclude) > 0:
|
|
1022
|
+
for ipaddress in self.__filter_consumer_ip_exclude:
|
|
1023
|
+
filters['sources']['exclude'].append({'ip_address': ipaddress})
|
|
1024
|
+
|
|
1025
|
+
provider_exclude_json = []
|
|
1026
|
+
if len(self._provider_exclude_labels) > 0:
|
|
1027
|
+
for label_href in self._provider_exclude_labels.keys():
|
|
1028
|
+
filters['destinations']['exclude'].append({'label': {'href': label_href}})
|
|
1029
|
+
|
|
1030
|
+
if len(self._provider_iplists_exclude) > 0:
|
|
1031
|
+
for iplist_href in self._provider_iplists_exclude.keys():
|
|
1032
|
+
filters['destinations']['exclude'].append({'ip_list': {'href': iplist_href}})
|
|
1033
|
+
|
|
1034
|
+
if len(self.__filter_provider_ip_exclude) > 0:
|
|
1035
|
+
for ipaddress in self.__filter_provider_ip_exclude:
|
|
1036
|
+
filters['destinations']['exclude'].append({'ip_address': ipaddress})
|
|
1037
|
+
|
|
1038
|
+
if len(self._include_direct_services) > 0:
|
|
1039
|
+
for service in self._include_direct_services:
|
|
1040
|
+
filters['services']['include'] .append(service.get_api_json())
|
|
1041
|
+
|
|
1042
|
+
if len(self._exclude_direct_services) > 0:
|
|
1043
|
+
for service in self._exclude_direct_services:
|
|
1044
|
+
filters['services']['exclude'].append(service.get_api_json())
|
|
1045
|
+
|
|
1046
|
+
if len(self._exclude_processes) > 0:
|
|
1047
|
+
for process in self._exclude_processes:
|
|
1048
|
+
filters['services']['exclude'].append({'process_name': process})
|
|
1049
|
+
|
|
1050
|
+
# print(filters)
|
|
1051
|
+
return filters
|
|
1052
|
+
|
|
1053
|
+
|
|
1054
|
+
class ExplorerQuery:
|
|
1055
|
+
def __init__(self, connector: APIConnector, max_results: int = 1500, max_running_time_seconds: int = 1800,
|
|
1056
|
+
check_for_update_interval_seconds: int = 10):
|
|
1057
|
+
self.api: APIConnector = connector
|
|
1058
|
+
self.filters = ExplorerFilterSetV1(max_results=max_results)
|
|
1059
|
+
self.results: Optional[ExplorerResultSetV1] = None
|
|
1060
|
+
self.max_running_time_seconds = max_running_time_seconds
|
|
1061
|
+
self.check_for_update_interval_seconds = check_for_update_interval_seconds
|
|
1062
|
+
|
|
1063
|
+
|
|
1064
|
+
def execute(self) -> ExplorerResultSetV1:
|
|
1065
|
+
"""
|
|
1066
|
+
Execute the query and stores the results in the 'results' property.
|
|
1067
|
+
It will also return said results for convenience.
|
|
1068
|
+
:return:
|
|
1069
|
+
"""
|
|
1070
|
+
self.results = self.api.explorer_search(self.filters, max_running_time_seconds=self.max_running_time_seconds,
|
|
1071
|
+
check_for_update_interval_seconds=self.check_for_update_interval_seconds)
|
|
1072
|
+
return self.results
|
|
1073
|
+
|
|
1074
|
+
|
|
1075
|
+
|
|
1076
|
+
|
|
1077
|
+
|