PyPANRestV2 2.1.0__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.
- pypanrestv2/ApplicationHelper.py +220 -0
- pypanrestv2/Base.py +1772 -0
- pypanrestv2/Device.py +21 -0
- pypanrestv2/Exceptions.py +11 -0
- pypanrestv2/Network.py +1722 -0
- pypanrestv2/Objects.py +1428 -0
- pypanrestv2/Panorama.py +426 -0
- pypanrestv2/Policies.py +755 -0
- pypanrestv2/XDR.py +299 -0
- pypanrestv2/__init__.py +4 -0
- pypanrestv2-2.1.0.dist-info/METADATA +209 -0
- pypanrestv2-2.1.0.dist-info/RECORD +13 -0
- pypanrestv2-2.1.0.dist-info/WHEEL +4 -0
pypanrestv2/Policies.py
ADDED
|
@@ -0,0 +1,755 @@
|
|
|
1
|
+
from typing import Optional, Dict, Any, Tuple, Union, List, Protocol, Set, TypeVar
|
|
2
|
+
from . import ApplicationHelper
|
|
3
|
+
from . import Exceptions
|
|
4
|
+
import pycountry
|
|
5
|
+
import ipaddress
|
|
6
|
+
import builtins
|
|
7
|
+
import time
|
|
8
|
+
import re
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from icecream import ic
|
|
11
|
+
import sys
|
|
12
|
+
import xmltodict
|
|
13
|
+
import dns.resolver
|
|
14
|
+
import requests
|
|
15
|
+
from requests.packages.urllib3.exceptions import InsecureRequestWarning
|
|
16
|
+
import logging
|
|
17
|
+
import xml.etree.ElementTree as ET
|
|
18
|
+
from pypanrestv2.Base import Base, PAN, Panorama, Firewall
|
|
19
|
+
import pypanrestv2.Objects
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
|
|
22
|
+
|
|
23
|
+
class Policy(Base, PAN):
|
|
24
|
+
"""
|
|
25
|
+
Base class for all policies
|
|
26
|
+
"""
|
|
27
|
+
valid_policies: List[str] = ['Security', 'NAT', 'QoS', 'PolicyBasedForwarding', 'Decryption',
|
|
28
|
+
'TunnelInspection', 'ApplicationOverride', 'Authentication', 'DoS', 'SDWAN']
|
|
29
|
+
|
|
30
|
+
def __init__(self, PANDevice: Panorama | Firewall, **kwargs):
|
|
31
|
+
Base.__init__(self, PANDevice, **kwargs)
|
|
32
|
+
PAN.__init__(self, PANDevice.base_url, api_key=PANDevice.api_key)
|
|
33
|
+
self.endpoint: str = 'Policies'
|
|
34
|
+
self._rulebase: str = kwargs.get('rulebase', None)
|
|
35
|
+
self.disabled: str = kwargs.get('disabled', 'no')
|
|
36
|
+
self.group_tag: str = kwargs.get('group_tag')
|
|
37
|
+
self.from_: Dict[str, List[str]] = kwargs.get('from_zone', {'member': []})
|
|
38
|
+
self.to: Dict[str, List[str]] = kwargs.get('to', {'member': []})
|
|
39
|
+
self.source: Dict[str, List[str]] = kwargs.get('source', {'member': []})
|
|
40
|
+
self.source_user: Dict[str, List[str]] = kwargs.get('source_user', {'member': []})
|
|
41
|
+
self.destination: Dict[str, List[str]] = kwargs.get('destination', {'member': []})
|
|
42
|
+
self.service: Dict[str, List[str]] = kwargs.get('service', {'member': []})
|
|
43
|
+
self.application: Dict[str, List[str]] = kwargs.get('application', {'member': []})
|
|
44
|
+
self.source_hip: Dict[str, List[str]] = kwargs.get('source_hip', {'member': []})
|
|
45
|
+
self.destination_hip: Dict[str, List[str]] = kwargs.get('destination_hip', {'member': []})
|
|
46
|
+
self.category: Dict[str, List[str]] = kwargs.get('category', {'member': []})
|
|
47
|
+
self.schedule: str = kwargs.get('schedule')
|
|
48
|
+
self.negate_source: str = kwargs.get('negate_source', 'no')
|
|
49
|
+
self.negate_destination: str = kwargs.get('negate_destination', 'no')
|
|
50
|
+
self.target: Dict[Dict, Dict, Str] = kwargs.get('target')
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def target(self) -> Dict:
|
|
54
|
+
return self._target
|
|
55
|
+
|
|
56
|
+
@target.setter
|
|
57
|
+
def target(self, value: Dict):
|
|
58
|
+
if value:
|
|
59
|
+
if not isinstance(self.PANDevice, Panorama):
|
|
60
|
+
raise TypeError("Target attribute can only be set for Panorama devices.")
|
|
61
|
+
|
|
62
|
+
if not isinstance(value, dict):
|
|
63
|
+
raise ValueError("The target value must be a dictionary.")
|
|
64
|
+
|
|
65
|
+
# Validate 'devices' key
|
|
66
|
+
if 'devices' not in value or not isinstance(value['devices'], dict) or 'entry' not in value['devices']:
|
|
67
|
+
raise ValueError("The 'devices' key must be a dictionary containing the 'entry' key.")
|
|
68
|
+
|
|
69
|
+
for entry in value['devices']['entry']:
|
|
70
|
+
if not isinstance(entry, dict) or '@name' not in entry:
|
|
71
|
+
raise ValueError("Each entry in 'devices' must be a dictionary with an '@name' key.")
|
|
72
|
+
if 'vsys' in entry and not (isinstance(entry['vsys'], dict) and 'entry' in entry['vsys']):
|
|
73
|
+
raise ValueError("If 'vsys' is provided, it must be a dictionary containing the 'entry' key.")
|
|
74
|
+
|
|
75
|
+
# Validate 'tags' key if provided
|
|
76
|
+
if 'tags' in value:
|
|
77
|
+
if not isinstance(value['tags'], dict) or 'member' not in value['tags'] or not isinstance(
|
|
78
|
+
value['tags']['member'], list):
|
|
79
|
+
raise ValueError("If 'tags' is provided, it must be a dictionary with a 'member' list.")
|
|
80
|
+
if not all(isinstance(tag, str) for tag in value['tags']['member']):
|
|
81
|
+
raise ValueError("All members in the 'tags' list must be strings.")
|
|
82
|
+
|
|
83
|
+
# Validate and set 'negate' key, default to 'no' if not provided
|
|
84
|
+
negate = value.get('negate', 'no')
|
|
85
|
+
if negate not in ['yes', 'no']:
|
|
86
|
+
raise ValueError("The 'negate' key must be either 'yes' or 'no'.")
|
|
87
|
+
|
|
88
|
+
# Set the target attribute if all validations pass
|
|
89
|
+
self._target = {
|
|
90
|
+
'devices': value['devices'],
|
|
91
|
+
'tags': value.get('tags', {'member': []}),
|
|
92
|
+
'negate': negate
|
|
93
|
+
}
|
|
94
|
+
self.entry.update({'target': self._target})
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def from_(self) -> Dict[str, List[str]]:
|
|
98
|
+
return self._from_
|
|
99
|
+
|
|
100
|
+
@from_.setter
|
|
101
|
+
def from_(self, value: Dict[str, List[str]]) -> None:
|
|
102
|
+
self.validate_member_dict(value, 'from_zone')
|
|
103
|
+
self._from_ = value
|
|
104
|
+
self.entry.update({'from': value})
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def to(self) -> Dict[str, List[str]]:
|
|
108
|
+
return self._to
|
|
109
|
+
|
|
110
|
+
@to.setter
|
|
111
|
+
def to(self, value: Dict[str, List[str]]) -> None:
|
|
112
|
+
self.validate_member_dict(value, 'to')
|
|
113
|
+
self._to = value
|
|
114
|
+
self.entry.update({'to': value})
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def source(self) -> Dict[str, List[str]]:
|
|
118
|
+
return self._source
|
|
119
|
+
|
|
120
|
+
@source.setter
|
|
121
|
+
def source(self, value: Dict[str, List[str]]) -> None:
|
|
122
|
+
self.validate_member_dict(value, 'source')
|
|
123
|
+
self._source = value
|
|
124
|
+
self.entry.update({'source': value})
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def source_user(self) -> Dict[str, List[str]]:
|
|
128
|
+
return self._source_user
|
|
129
|
+
|
|
130
|
+
@source_user.setter
|
|
131
|
+
def source_user(self, value: Dict[str, List[str]]) -> None:
|
|
132
|
+
self.validate_member_dict(value, 'source_user')
|
|
133
|
+
self._source_user = value
|
|
134
|
+
self.entry.update({'source-user': value})
|
|
135
|
+
|
|
136
|
+
@property
|
|
137
|
+
def destination(self) -> Dict[str, List[str]]:
|
|
138
|
+
return self._destination
|
|
139
|
+
|
|
140
|
+
@destination.setter
|
|
141
|
+
def destination(self, value: Dict[str, List[str]]) -> None:
|
|
142
|
+
self.validate_member_dict(value, 'destination')
|
|
143
|
+
self._destination = value
|
|
144
|
+
self.entry.update({'destination': value})
|
|
145
|
+
|
|
146
|
+
@property
|
|
147
|
+
def service(self) -> Dict[str, List[str]]:
|
|
148
|
+
return self._service
|
|
149
|
+
|
|
150
|
+
@service.setter
|
|
151
|
+
def service(self, value: Dict[str, List[str]]) -> None:
|
|
152
|
+
self.validate_member_dict(value, 'service')
|
|
153
|
+
self._service = value
|
|
154
|
+
self.entry.update({'service': value})
|
|
155
|
+
|
|
156
|
+
@property
|
|
157
|
+
def application(self) -> Dict[str, List[str]]:
|
|
158
|
+
return self._application
|
|
159
|
+
|
|
160
|
+
@application.setter
|
|
161
|
+
def application(self, value: Dict[str, List[str]]) -> None:
|
|
162
|
+
self.validate_member_dict(value, 'application')
|
|
163
|
+
self._application = value
|
|
164
|
+
self.entry.update({'application': value})
|
|
165
|
+
|
|
166
|
+
@property
|
|
167
|
+
def source_hip(self) -> Dict[str, List[str]]:
|
|
168
|
+
return self._source_hip
|
|
169
|
+
|
|
170
|
+
@source_hip.setter
|
|
171
|
+
def source_hip(self, value: Dict[str, List[str]]) -> None:
|
|
172
|
+
self.validate_member_dict(value, 'source_hip')
|
|
173
|
+
self._source_hip = value
|
|
174
|
+
self.entry.update({'source-hip': value})
|
|
175
|
+
|
|
176
|
+
@property
|
|
177
|
+
def destination_hip(self) -> Dict[str, List[str]]:
|
|
178
|
+
return self._destination_hip
|
|
179
|
+
|
|
180
|
+
@destination_hip.setter
|
|
181
|
+
def destination_hip(self, value: Dict[str, List[str]]) -> None:
|
|
182
|
+
self.validate_member_dict(value, 'destination_hip')
|
|
183
|
+
self._destination_hip = value
|
|
184
|
+
self.entry.update({'destination-hip': value})
|
|
185
|
+
|
|
186
|
+
@property
|
|
187
|
+
def schedule(self) -> str:
|
|
188
|
+
return self._schedule
|
|
189
|
+
|
|
190
|
+
@schedule.setter
|
|
191
|
+
def schedule(self, value: str) -> None:
|
|
192
|
+
if value:
|
|
193
|
+
if not isinstance(value, str):
|
|
194
|
+
raise TypeError("Schedule must be a string.")
|
|
195
|
+
self._schedule = value
|
|
196
|
+
self.entry.update({'schedule': value})
|
|
197
|
+
|
|
198
|
+
@property
|
|
199
|
+
def negate_source(self) -> str:
|
|
200
|
+
return self._negate_source
|
|
201
|
+
|
|
202
|
+
@negate_source.setter
|
|
203
|
+
def negate_source(self, value: str = 'no') -> None:
|
|
204
|
+
if value not in ['yes', 'no']:
|
|
205
|
+
raise ValueError("negate_source must be either 'yes' or 'no'.")
|
|
206
|
+
self._negate_source = value
|
|
207
|
+
self.entry.update({'negate-source': value})
|
|
208
|
+
|
|
209
|
+
@property
|
|
210
|
+
def negate_destination(self) -> str:
|
|
211
|
+
return self._negate_destination
|
|
212
|
+
|
|
213
|
+
@negate_destination.setter
|
|
214
|
+
def negate_destination(self, value: str = 'no') -> None:
|
|
215
|
+
if value not in ['yes', 'no']:
|
|
216
|
+
raise ValueError("negate_destination must be either 'yes' or 'no'.")
|
|
217
|
+
self._negate_destination = value
|
|
218
|
+
self.entry.update({'negate-destination': value})
|
|
219
|
+
|
|
220
|
+
@property
|
|
221
|
+
def category(self) -> str:
|
|
222
|
+
return self._category
|
|
223
|
+
|
|
224
|
+
@category.setter
|
|
225
|
+
def category(self, value: Dict[str, List[str]]) -> None:
|
|
226
|
+
self.validate_member_dict(value, 'category')
|
|
227
|
+
self._category = value
|
|
228
|
+
self.entry.update({'category': value})
|
|
229
|
+
|
|
230
|
+
@property
|
|
231
|
+
def disabled(self) -> str:
|
|
232
|
+
return self._disabled
|
|
233
|
+
|
|
234
|
+
@disabled.setter
|
|
235
|
+
def disabled(self, value: str = 'no') -> None:
|
|
236
|
+
if value not in ['yes', 'no']:
|
|
237
|
+
raise ValueError("disabled must be either 'yes' or 'no'.")
|
|
238
|
+
self._disabled = value
|
|
239
|
+
self.entry.update({'disabled': value})
|
|
240
|
+
|
|
241
|
+
@property
|
|
242
|
+
def group_tag(self) -> str:
|
|
243
|
+
return self._group_tag
|
|
244
|
+
|
|
245
|
+
@group_tag.setter
|
|
246
|
+
def group_tag(self, value: str) -> None:
|
|
247
|
+
if value:
|
|
248
|
+
if not isinstance(value, str):
|
|
249
|
+
raise TypeError("group_tag must be a string.")
|
|
250
|
+
if len(value) > 127:
|
|
251
|
+
raise ValueError("group_tag cannot exceed 127 characters in length.")
|
|
252
|
+
self._group_tag = value
|
|
253
|
+
self.entry.update({'group-tag': value})
|
|
254
|
+
|
|
255
|
+
@property
|
|
256
|
+
def rulebase(self) -> str:
|
|
257
|
+
return self._rulebase
|
|
258
|
+
|
|
259
|
+
@rulebase.setter
|
|
260
|
+
def rulebase(self, value: str) -> None:
|
|
261
|
+
if isinstance(self.PANDevice, Panorama):
|
|
262
|
+
formatted_value = value.capitalize() # This will make the first letter uppercase and the rest lowercase
|
|
263
|
+
|
|
264
|
+
if formatted_value not in ['Pre', 'Post']:
|
|
265
|
+
raise ValueError("rulebase must be either 'Pre' or 'Post'.")
|
|
266
|
+
|
|
267
|
+
self._rulebase = formatted_value
|
|
268
|
+
else:
|
|
269
|
+
self._rulebase = None
|
|
270
|
+
|
|
271
|
+
class SecurityRules(Policy):
|
|
272
|
+
VALID_ACTIONS = ["deny", "allow", "drop", "reset-client", "reset-server", "reset-both"]
|
|
273
|
+
|
|
274
|
+
def __init__(self, PANDevice: Panorama | Firewall, **kwargs):
|
|
275
|
+
super().__init__(PANDevice, max_name_length=63, max_description_length=1024, **kwargs)
|
|
276
|
+
self.action: str = kwargs.get('action', 'allow')
|
|
277
|
+
self.icmp_unreachable: str = kwargs.get('icmp_unreachable', 'no')
|
|
278
|
+
self.disable_inspect: str = kwargs.get('disable_inspect', 'no')
|
|
279
|
+
self.rule_type: str = kwargs.get('rule_type', 'universal')
|
|
280
|
+
self.option: dict = kwargs.get('option', {'disable-server-response-inspection': 'no'})
|
|
281
|
+
self.log_setting: str = kwargs.get('log_setting', '')
|
|
282
|
+
self.log_start: str = kwargs.get('log_start', 'no')
|
|
283
|
+
self.log_end: str = kwargs.get('log_end', 'yes')
|
|
284
|
+
self.profile_setting: dict = kwargs.get('profile_setting', {})
|
|
285
|
+
self.qos: dict = kwargs.get('qos')
|
|
286
|
+
|
|
287
|
+
@property
|
|
288
|
+
def action(self) -> str:
|
|
289
|
+
return self._action
|
|
290
|
+
|
|
291
|
+
@action.setter
|
|
292
|
+
def action(self, value: str) -> None:
|
|
293
|
+
if not isinstance(value, str):
|
|
294
|
+
raise TypeError("Action must be a string.")
|
|
295
|
+
|
|
296
|
+
if value not in self.VALID_ACTIONS:
|
|
297
|
+
raise ValueError(f"Invalid action: {value}. Valid actions are: {', '.join(self.VALID_ACTIONS)}")
|
|
298
|
+
|
|
299
|
+
self._action = value
|
|
300
|
+
self.entry.update({'action': value})
|
|
301
|
+
|
|
302
|
+
@property
|
|
303
|
+
def icmp_unreachable(self) -> str:
|
|
304
|
+
return self._icmp_unreachable
|
|
305
|
+
|
|
306
|
+
@icmp_unreachable.setter
|
|
307
|
+
def icmp_unreachable(self, value: str) -> None:
|
|
308
|
+
if not isinstance(value, str):
|
|
309
|
+
raise TypeError("icmp_unreachable must be a string.")
|
|
310
|
+
|
|
311
|
+
if value.lower() not in ['yes', 'no']:
|
|
312
|
+
raise ValueError("icmp_unreachable must be either 'yes' or 'no'.")
|
|
313
|
+
|
|
314
|
+
self._icmp_unreachable = value.lower()
|
|
315
|
+
self.entry.update({'icmp-unreachable': value})
|
|
316
|
+
|
|
317
|
+
@property
|
|
318
|
+
def disable_inspect(self) -> str:
|
|
319
|
+
return self._disable_inspect
|
|
320
|
+
|
|
321
|
+
@disable_inspect.setter
|
|
322
|
+
def disable_inspect(self, value: str) -> None:
|
|
323
|
+
if not isinstance(value, str):
|
|
324
|
+
raise TypeError("disable_inspect must be a string.")
|
|
325
|
+
|
|
326
|
+
value_lower = value.lower()
|
|
327
|
+
if value_lower not in ['yes', 'no']:
|
|
328
|
+
raise ValueError("disable_inspect must be either 'yes' or 'no'.")
|
|
329
|
+
|
|
330
|
+
self._disable_inspect = value_lower
|
|
331
|
+
self.entry.update({'disable-inspect':value})
|
|
332
|
+
|
|
333
|
+
@property
|
|
334
|
+
def rule_type(self) -> str:
|
|
335
|
+
return self._rule_type
|
|
336
|
+
|
|
337
|
+
@rule_type.setter
|
|
338
|
+
def rule_type(self, value: str) -> None:
|
|
339
|
+
if not isinstance(value, str):
|
|
340
|
+
raise TypeError("rule_type must be a string.")
|
|
341
|
+
|
|
342
|
+
value_lower = value.lower()
|
|
343
|
+
valid_types = ["universal", "intrazone", "interzone"]
|
|
344
|
+
if value_lower not in valid_types:
|
|
345
|
+
raise ValueError(f"rule_type must be one of: {', '.join(valid_types)}")
|
|
346
|
+
|
|
347
|
+
self._rule_type = value_lower
|
|
348
|
+
self.entry.update({'rule-type': value_lower})
|
|
349
|
+
|
|
350
|
+
@property
|
|
351
|
+
def option(self) -> dict:
|
|
352
|
+
return self._option
|
|
353
|
+
|
|
354
|
+
@option.setter
|
|
355
|
+
def option(self, value: dict) -> None:
|
|
356
|
+
if not isinstance(value, dict):
|
|
357
|
+
raise TypeError("option must be a dictionary.")
|
|
358
|
+
|
|
359
|
+
# Defaulting to 'no' if not specified
|
|
360
|
+
dsri = value.get('disable-server-response-inspection', 'no').lower()
|
|
361
|
+
|
|
362
|
+
if dsri not in ['yes', 'no']:
|
|
363
|
+
raise ValueError("disable-server-response-inspection must be 'yes' or 'no'.")
|
|
364
|
+
|
|
365
|
+
# Update the dictionary with the validated value
|
|
366
|
+
value['disable-server-response-inspection'] = dsri
|
|
367
|
+
|
|
368
|
+
self._option = value
|
|
369
|
+
self.entry.update({'option': value})
|
|
370
|
+
|
|
371
|
+
@property
|
|
372
|
+
def log_setting(self) -> str:
|
|
373
|
+
return self._log_setting
|
|
374
|
+
|
|
375
|
+
@log_setting.setter
|
|
376
|
+
def log_setting(self, value: str) -> None:
|
|
377
|
+
if not isinstance(value, str):
|
|
378
|
+
raise TypeError("log_setting must be a string.")
|
|
379
|
+
|
|
380
|
+
if len(value) > 63:
|
|
381
|
+
raise ValueError("log_setting cannot exceed 63 characters.")
|
|
382
|
+
|
|
383
|
+
self._log_setting = value
|
|
384
|
+
self.entry.update({'log-setting': value})
|
|
385
|
+
|
|
386
|
+
@property
|
|
387
|
+
def log_start(self) -> str:
|
|
388
|
+
return self._log_start
|
|
389
|
+
|
|
390
|
+
@log_start.setter
|
|
391
|
+
def log_start(self, value: str) -> None:
|
|
392
|
+
if value not in ['yes', 'no']:
|
|
393
|
+
raise ValueError("log_start must be 'yes' or 'no'.")
|
|
394
|
+
|
|
395
|
+
self._log_start = value
|
|
396
|
+
# Updating the entry dictionary with key 'log-start'
|
|
397
|
+
self.entry.update({'log-start': value})
|
|
398
|
+
|
|
399
|
+
@property
|
|
400
|
+
def log_end(self) -> str:
|
|
401
|
+
return self._log_end
|
|
402
|
+
|
|
403
|
+
@log_end.setter
|
|
404
|
+
def log_end(self, value: str) -> None:
|
|
405
|
+
if value not in ['yes', 'no']:
|
|
406
|
+
raise ValueError("log_end must be either 'yes' or 'no'.")
|
|
407
|
+
self._log_end = value
|
|
408
|
+
# Updating the entry dictionary with key 'log-end'
|
|
409
|
+
self.entry.update({'log-end': value})
|
|
410
|
+
|
|
411
|
+
@property
|
|
412
|
+
def profile_setting(self) -> dict:
|
|
413
|
+
return self._profile_setting
|
|
414
|
+
|
|
415
|
+
@profile_setting.setter
|
|
416
|
+
def profile_setting(self, value: dict) -> None:
|
|
417
|
+
if value:
|
|
418
|
+
if not isinstance(value, dict):
|
|
419
|
+
raise TypeError("profile_setting must be a dictionary.")
|
|
420
|
+
|
|
421
|
+
if 'profiles' in value:
|
|
422
|
+
self._validate_profiles(value['profiles'])
|
|
423
|
+
elif 'group' in value:
|
|
424
|
+
self._validate_group(value['group'])
|
|
425
|
+
else:
|
|
426
|
+
raise ValueError("profile_setting must have either 'profiles' or 'group' as keys.")
|
|
427
|
+
|
|
428
|
+
self._profile_setting = value
|
|
429
|
+
self.entry.update({'profile-setting': value})
|
|
430
|
+
|
|
431
|
+
@staticmethod
|
|
432
|
+
def _validate_profiles(profiles: dict) -> None:
|
|
433
|
+
valid_profile_keys = ['url-filtering', 'data-filtering', 'file-blocking',
|
|
434
|
+
'wildfire-analysis', 'virus', 'spyware', 'vulnerability']
|
|
435
|
+
for key, profile in profiles.items():
|
|
436
|
+
if key not in valid_profile_keys:
|
|
437
|
+
raise ValueError(f"Invalid profile key: {key}.")
|
|
438
|
+
if not isinstance(profile, dict) or 'member' not in profile or not isinstance(profile['member'], list):
|
|
439
|
+
raise ValueError(
|
|
440
|
+
f"Each profile in profiles must be a dictionary with a 'member' list. Issue found in {key}.")
|
|
441
|
+
if not all(isinstance(item, str) for item in profile['member']):
|
|
442
|
+
raise ValueError(f"All items in the 'member' list of {key} must be strings.")
|
|
443
|
+
|
|
444
|
+
@staticmethod
|
|
445
|
+
def _validate_group(group: dict) -> None:
|
|
446
|
+
if not isinstance(group, dict) or 'member' not in group or not isinstance(group['member'], list):
|
|
447
|
+
raise ValueError("group must be a dictionary with a 'member' list.")
|
|
448
|
+
if not all(isinstance(item, str) for item in group['member']):
|
|
449
|
+
raise ValueError("All items in the 'member' list of group must be strings.")
|
|
450
|
+
|
|
451
|
+
@property
|
|
452
|
+
def qos(self) -> dict:
|
|
453
|
+
return self._qos
|
|
454
|
+
|
|
455
|
+
@qos.setter
|
|
456
|
+
def qos(self, value: dict) -> None:
|
|
457
|
+
if value:
|
|
458
|
+
if not isinstance(value, dict):
|
|
459
|
+
raise TypeError("qos must be a dictionary.")
|
|
460
|
+
|
|
461
|
+
if 'marking' not in value or not isinstance(value['marking'], dict):
|
|
462
|
+
raise ValueError("qos must contain a 'marking' dictionary.")
|
|
463
|
+
|
|
464
|
+
marking = value['marking']
|
|
465
|
+
valid_keys = ['ip-dscp', 'ip-precedence', 'follow-c2s-flow']
|
|
466
|
+
if len(marking) != 1 or not set(marking.keys()).issubset(set(valid_keys)):
|
|
467
|
+
raise ValueError(f"marking must contain exactly one of the following keys: {', '.join(valid_keys)}")
|
|
468
|
+
|
|
469
|
+
if 'ip-dscp' in marking and not isinstance(marking['ip-dscp'], str):
|
|
470
|
+
raise ValueError("The value for 'ip-dscp' must be a string.")
|
|
471
|
+
if 'ip-precedence' in marking and not isinstance(marking['ip-precedence'], str):
|
|
472
|
+
raise ValueError("The value for 'ip-precedence' must be a string.")
|
|
473
|
+
if 'follow-c2s-flow' in marking and marking['follow-c2s-flow'] != {}:
|
|
474
|
+
raise ValueError("The value for 'follow-c2s-flow' must be an empty dictionary.")
|
|
475
|
+
|
|
476
|
+
self._qos = value
|
|
477
|
+
self.entry.update({'qos': value})
|
|
478
|
+
|
|
479
|
+
class NatRules(Policy):
|
|
480
|
+
def __init__(self, PANDevice: Panorama | Firewall, **kwargs):
|
|
481
|
+
super().__init__(PANDevice, max_name_length=63, max_description_length=1024, **kwargs)
|
|
482
|
+
self.destination_translation: dict = kwargs.get('destination_translation')
|
|
483
|
+
self.dynamic_destination_translation: dict = kwargs.get('dynamic_destination_translation')
|
|
484
|
+
self.active_active_device_binding: str = kwargs.get('active_active_device_binding')
|
|
485
|
+
self.service: str = kwargs.get('service', 'any')
|
|
486
|
+
self.nat_type: str = kwargs.get('nat_type', 'ipv4')
|
|
487
|
+
self.to_interface: str = kwargs.get('to_interface', 'any')
|
|
488
|
+
self.source_translation: dict = kwargs.get('source_translation')
|
|
489
|
+
|
|
490
|
+
@staticmethod
|
|
491
|
+
def _validate_translation(value: Dict, keys: List[str]) -> None:
|
|
492
|
+
for key in keys:
|
|
493
|
+
if key == 'translated-address' and key not in value:
|
|
494
|
+
raise ValueError(f"'{key}' is required in the translation dictionary.")
|
|
495
|
+
if key == 'translated-port' and key in value:
|
|
496
|
+
if not (1 <= value['translated-port'] <= 65535):
|
|
497
|
+
raise ValueError("translated-port must be between 1 and 65535.")
|
|
498
|
+
|
|
499
|
+
@property
|
|
500
|
+
def destination_translation(self) -> Optional[Dict]:
|
|
501
|
+
return self._destination_translation
|
|
502
|
+
|
|
503
|
+
@destination_translation.setter
|
|
504
|
+
def destination_translation(self, value: Dict) -> None:
|
|
505
|
+
if value:
|
|
506
|
+
if self._dynamic_destination_translation is not None:
|
|
507
|
+
raise ValueError("Cannot set destination_translation when dynamic_destination_translation is already set.")
|
|
508
|
+
if not isinstance(value, dict):
|
|
509
|
+
raise TypeError("destination_translation must be a dictionary.")
|
|
510
|
+
|
|
511
|
+
# Validate the keys and their values
|
|
512
|
+
self._validate_translation(value, ['translated-address', 'translated-port'])
|
|
513
|
+
|
|
514
|
+
if 'dns-rewrite' in value:
|
|
515
|
+
if 'direction' not in value['dns-rewrite'] or value['dns-rewrite']['direction'] not in ['reverse',
|
|
516
|
+
'forward']:
|
|
517
|
+
raise ValueError("Invalid 'direction' value in 'dns-rewrite'.")
|
|
518
|
+
|
|
519
|
+
self._destination_translation = value
|
|
520
|
+
self.entry.update({'destination-translation': value})
|
|
521
|
+
|
|
522
|
+
@property
|
|
523
|
+
def dynamic_destination_translation(self) -> Optional[Dict]:
|
|
524
|
+
return self._dynamic_destination_translation
|
|
525
|
+
|
|
526
|
+
@dynamic_destination_translation.setter
|
|
527
|
+
def dynamic_destination_translation(self, value: Dict) -> None:
|
|
528
|
+
if value:
|
|
529
|
+
if self._destination_translation is not None:
|
|
530
|
+
raise ValueError("Cannot set dynamic_destination_translation when destination_translation is already set.")
|
|
531
|
+
if not isinstance(value, dict):
|
|
532
|
+
raise TypeError("dynamic_destination_translation must be a dictionary.")
|
|
533
|
+
|
|
534
|
+
# Validate the keys and their values
|
|
535
|
+
self._validate_translation(value, ['translated-address', 'translated-port'])
|
|
536
|
+
|
|
537
|
+
if 'distribution' in value and value['distribution'] not in ["round-robin", "source-ip-hash", "ip-modulo",
|
|
538
|
+
"ip-hash", "least-sessions"]:
|
|
539
|
+
raise ValueError("Invalid 'distribution' value.")
|
|
540
|
+
|
|
541
|
+
self._dynamic_destination_translation = value
|
|
542
|
+
self.entry.update({'dynamic-destination-translation': value})
|
|
543
|
+
|
|
544
|
+
@property
|
|
545
|
+
def active_active_device_binding(self) -> str:
|
|
546
|
+
return self._active_active_device_binding
|
|
547
|
+
|
|
548
|
+
@active_active_device_binding.setter
|
|
549
|
+
def active_active_device_binding(self, value: str) -> None:
|
|
550
|
+
if value:
|
|
551
|
+
if value not in ["primary", "both", "0", "1"]:
|
|
552
|
+
raise ValueError("active_active_device_binding must be one of 'primary', 'both', '0', or '1'.")
|
|
553
|
+
self._active_active_device_binding = value
|
|
554
|
+
self.entry.update({'active-active-device-binding': value})
|
|
555
|
+
|
|
556
|
+
@property
|
|
557
|
+
def service(self) -> str:
|
|
558
|
+
return self._service
|
|
559
|
+
|
|
560
|
+
@service.setter
|
|
561
|
+
def service(self, value: str) -> None:
|
|
562
|
+
if value == 'any':
|
|
563
|
+
self._service = 'any'
|
|
564
|
+
else:
|
|
565
|
+
check_service = Services(self.PANDevice, name=value, location=self.location, device_group=self.device_group)
|
|
566
|
+
if check_service.get(ANYLOCATION=True, IsSearch=True):
|
|
567
|
+
self._service = value
|
|
568
|
+
else:
|
|
569
|
+
raise ValueError(f"The service '{value}' does not exist.")
|
|
570
|
+
|
|
571
|
+
# Update the entry dictionary
|
|
572
|
+
self.entry.update({'service': self._service})
|
|
573
|
+
|
|
574
|
+
@property
|
|
575
|
+
def nat_type(self) -> str:
|
|
576
|
+
return self._nat_type
|
|
577
|
+
|
|
578
|
+
@nat_type.setter
|
|
579
|
+
def nat_type(self, value: str) -> None:
|
|
580
|
+
valid_types = ["ipv4", "nat64", "nptv6"]
|
|
581
|
+
if value not in valid_types:
|
|
582
|
+
raise ValueError(f"Invalid nat_type: {value}. Must be one of: {', '.join(valid_types)}")
|
|
583
|
+
|
|
584
|
+
self._nat_type = value
|
|
585
|
+
# Update the entry dictionary to reflect the change
|
|
586
|
+
self.entry.update({'nat-type': self._nat_type})
|
|
587
|
+
|
|
588
|
+
@property
|
|
589
|
+
def to_interface(self) -> str:
|
|
590
|
+
return self._to_interface
|
|
591
|
+
|
|
592
|
+
@to_interface.setter
|
|
593
|
+
def to_interface(self, value: str) -> None:
|
|
594
|
+
if not isinstance(value, str):
|
|
595
|
+
raise TypeError("to_interface must be a string.")
|
|
596
|
+
|
|
597
|
+
self._to_interface = value
|
|
598
|
+
# Update the entry dictionary to reflect the change
|
|
599
|
+
self.entry.update({'to-interface': self._to_interface})
|
|
600
|
+
|
|
601
|
+
@property
|
|
602
|
+
def source_translation(self) -> dict:
|
|
603
|
+
return self._source_translation
|
|
604
|
+
|
|
605
|
+
@source_translation.setter
|
|
606
|
+
def source_translation(self, value: dict) -> None:
|
|
607
|
+
if not isinstance(value, dict):
|
|
608
|
+
raise TypeError("source_translation must be a dictionary.")
|
|
609
|
+
|
|
610
|
+
# Validate the keys of the main dict
|
|
611
|
+
valid_keys = ['dynamic-ip-and-port', 'dynamic-ip', 'static-ip']
|
|
612
|
+
if all(key not in valid_keys for key in value.keys()):
|
|
613
|
+
raise ValueError(f"Invalid key in source_translation. Must be one of: {', '.join(valid_keys)}")
|
|
614
|
+
|
|
615
|
+
keys_present = [key for key in valid_keys if key in value]
|
|
616
|
+
if len(keys_present) > 1:
|
|
617
|
+
raise ValueError("Only one of 'dynamic-ip-and-port', 'dynamic-ip', or 'static-ip' can be set at a time.")
|
|
618
|
+
|
|
619
|
+
if 'dynamic-ip-and-port' in value:
|
|
620
|
+
self._validate_dynamic_ip_and_port(value['dynamic-ip-and-port'])
|
|
621
|
+
elif 'dynamic-ip' in value:
|
|
622
|
+
self._validate_dynamic_ip(value['dynamic-ip'])
|
|
623
|
+
elif 'static-ip' in value:
|
|
624
|
+
self._validate_static_ip(value['static-ip'])
|
|
625
|
+
|
|
626
|
+
# Update the entry dictionary to reflect the change
|
|
627
|
+
self._source_translation = value
|
|
628
|
+
self.entry.update({'source-translation': self._source_translation})
|
|
629
|
+
|
|
630
|
+
def _validate_dynamic_ip_and_port(self, value: dict) -> None:
|
|
631
|
+
valid_sub_keys = ['translated-addresses', 'interface-address']
|
|
632
|
+
if all(sub_key not in valid_sub_keys for sub_key in value.keys()):
|
|
633
|
+
raise ValueError(f"Invalid key in dynamic-ip-and-port. Must be one of: {', '.join(valid_sub_keys)}")
|
|
634
|
+
|
|
635
|
+
# Validate 'translated-addresses'
|
|
636
|
+
if 'translated-addresses' in value:
|
|
637
|
+
if not isinstance(value['translated-addresses'], dict) or 'member' not in value['translated-addresses']:
|
|
638
|
+
raise ValueError("translated-addresses must be a dict with a 'member' key.")
|
|
639
|
+
if not all(isinstance(item, str) for item in value['translated-addresses']['member']):
|
|
640
|
+
raise ValueError("All items in 'member' list of translated-addresses must be strings.")
|
|
641
|
+
|
|
642
|
+
# Validate 'interface-address'
|
|
643
|
+
if 'interface-address' in value:
|
|
644
|
+
self._validate_interface_address(value['interface-address'])
|
|
645
|
+
|
|
646
|
+
def _validate_interface_address(self, value: dict) -> None:
|
|
647
|
+
valid_ip_keys = ['ip', 'floating-ip']
|
|
648
|
+
if all(ip_key not in valid_ip_keys for ip_key in value.keys()):
|
|
649
|
+
raise ValueError(f"Invalid key in interface-address. Must be one of: {', '.join(valid_ip_keys)}")
|
|
650
|
+
|
|
651
|
+
# Validate 'ip' key
|
|
652
|
+
if 'ip' in value:
|
|
653
|
+
self._validate_ip_or_floating_ip(value['ip'], 'ip')
|
|
654
|
+
|
|
655
|
+
# Validate 'floating-ip' key
|
|
656
|
+
if 'floating-ip' in value:
|
|
657
|
+
self._validate_ip_or_floating_ip(value['floating-ip'], 'floating-ip')
|
|
658
|
+
|
|
659
|
+
@staticmethod
|
|
660
|
+
def _validate_ip_or_floating_ip(value: dict, key: str) -> None:
|
|
661
|
+
if not isinstance(value, dict):
|
|
662
|
+
raise ValueError(f"{key} must be a dictionary.")
|
|
663
|
+
|
|
664
|
+
# Interface is required
|
|
665
|
+
if 'interface' not in value or not isinstance(value['interface'], str) or len(value['interface']) > 31:
|
|
666
|
+
raise ValueError(f"interface in {key} is required and must be a string up to 31 characters long.")
|
|
667
|
+
|
|
668
|
+
# IP or floating-ip is optional
|
|
669
|
+
if key in value and (not isinstance(value[key], str) or not value[key]):
|
|
670
|
+
raise ValueError(f"{key} must be a non-empty string if provided.")
|
|
671
|
+
|
|
672
|
+
def _validate_dynamic_ip(self, value: dict) -> None:
|
|
673
|
+
required_keys = ['translated-addresses']
|
|
674
|
+
for key in required_keys:
|
|
675
|
+
if key not in value:
|
|
676
|
+
raise ValueError(f"'{key}' is required in 'dynamic-ip'.")
|
|
677
|
+
|
|
678
|
+
# Validate 'translated-addresses' in 'dynamic-ip'
|
|
679
|
+
if 'translated-addresses' in value:
|
|
680
|
+
if not isinstance(value['translated-addresses'], dict) or 'member' not in value['translated-addresses']:
|
|
681
|
+
raise ValueError("'translated-addresses' must be a dict with a 'member' key.")
|
|
682
|
+
if not all(isinstance(item, str) for item in value['translated-addresses']['member']):
|
|
683
|
+
raise ValueError("All items in 'member' list of 'translated-addresses' must be strings.")
|
|
684
|
+
|
|
685
|
+
# Validate 'fallback' in 'dynamic-ip'
|
|
686
|
+
if 'fallback' in value:
|
|
687
|
+
if not isinstance(value['fallback'], dict):
|
|
688
|
+
raise ValueError("'fallback' in 'dynamic-ip' must be a dictionary.")
|
|
689
|
+
for fallback_key, fallback_val in value['fallback'].items():
|
|
690
|
+
if fallback_key not in ['translated-addresses', 'interface-address']:
|
|
691
|
+
raise ValueError(f"Invalid key in 'fallback': {fallback_key}")
|
|
692
|
+
if fallback_key == 'translated-addresses':
|
|
693
|
+
self._validate_translated_addresses(fallback_val)
|
|
694
|
+
elif fallback_key == 'interface-address':
|
|
695
|
+
self._validate_interface_address(fallback_val)
|
|
696
|
+
|
|
697
|
+
@staticmethod
|
|
698
|
+
def _validate_translated_addresses(value: dict) -> None:
|
|
699
|
+
if not isinstance(value, dict) or 'member' not in value:
|
|
700
|
+
raise ValueError("'translated-addresses' must be a dict with a 'member' key.")
|
|
701
|
+
if not all(isinstance(item, str) for item in value['member']):
|
|
702
|
+
raise ValueError("All items in 'member' list of 'translated-addresses' must be strings.")
|
|
703
|
+
|
|
704
|
+
def _validate_static_ip(self, value: dict) -> None:
|
|
705
|
+
if 'translated-address' not in value:
|
|
706
|
+
raise ValueError("translated-address is required in static-ip.")
|
|
707
|
+
|
|
708
|
+
# Validate 'translated-address'
|
|
709
|
+
if not isinstance(value['translated-address'], str):
|
|
710
|
+
raise ValueError("translated-address in static-ip must be a string.")
|
|
711
|
+
|
|
712
|
+
# Ensure 'bi-directional' key exists, default to 'no'
|
|
713
|
+
bi_directional = value.get('bi-directional', 'no')
|
|
714
|
+
# Validate 'bi-directional'
|
|
715
|
+
if bi_directional not in self.yes_no:
|
|
716
|
+
raise ValueError("bi-directional in static-ip must be 'yes' or 'no'.")
|
|
717
|
+
|
|
718
|
+
# Set the validated value back in case it was missing
|
|
719
|
+
value['bi-directional'] = bi_directional
|
|
720
|
+
|
|
721
|
+
class QoSRules(Policy):
|
|
722
|
+
def __init__(self, PANDevice: Panorama | Firewall, **kwargs):
|
|
723
|
+
super().__init__(PANDevice, max_name_length=63, max_description_length=1024, **kwargs)
|
|
724
|
+
|
|
725
|
+
class PolicyBasedForwardingRules(Policy):
|
|
726
|
+
def __init__(self, PANDevice: Panorama | Firewall, **kwargs):
|
|
727
|
+
super().__init__(PANDevice, max_name_length=63, max_description_length=1024, **kwargs)
|
|
728
|
+
|
|
729
|
+
class DecryptionRules(Policy):
|
|
730
|
+
def __init__(self, PANDevice: Panorama | Firewall, **kwargs):
|
|
731
|
+
super().__init__(PANDevice, max_name_length=63, max_description_length=1024, **kwargs)
|
|
732
|
+
|
|
733
|
+
class NetworkPacketBrokerRules(Policy):
|
|
734
|
+
def __init__(self, PANDevice: Panorama | Firewall, **kwargs):
|
|
735
|
+
super().__init__(PANDevice, max_name_length=63, max_description_length=1024, **kwargs)
|
|
736
|
+
|
|
737
|
+
class TunnelInspectionRules(Policy):
|
|
738
|
+
def __init__(self, PANDevice: Panorama | Firewall, **kwargs):
|
|
739
|
+
super().__init__(PANDevice, max_name_length=63, max_description_length=1024, **kwargs)
|
|
740
|
+
|
|
741
|
+
class ApplicationOverrideRules(Policy):
|
|
742
|
+
def __init__(self, PANDevice: Panorama | Firewall, **kwargs):
|
|
743
|
+
super().__init__(PANDevice, max_name_length=63, max_description_length=1024, **kwargs)
|
|
744
|
+
|
|
745
|
+
class AuthenticationRules(Policy):
|
|
746
|
+
def __init__(self, PANDevice: Panorama | Firewall, **kwargs):
|
|
747
|
+
super().__init__(PANDevice, max_name_length=63, max_description_length=1024, **kwargs)
|
|
748
|
+
|
|
749
|
+
class DoSRules(Policy):
|
|
750
|
+
def __init__(self, PANDevice: Panorama | Firewall, **kwargs):
|
|
751
|
+
super().__init__(PANDevice, max_name_length=63, max_description_length=1024, **kwargs)
|
|
752
|
+
|
|
753
|
+
class SDWANRules(Policy):
|
|
754
|
+
def __init__(self, PANDevice: Panorama | Firewall, **kwargs):
|
|
755
|
+
super().__init__(PANDevice, max_name_length=63, max_description_length=1024, **kwargs)
|