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/Objects.py
ADDED
|
@@ -0,0 +1,1428 @@
|
|
|
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
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Object(Base, PAN):
|
|
24
|
+
"""
|
|
25
|
+
Base class for all objects
|
|
26
|
+
"""
|
|
27
|
+
valid_objects: List[str] = [
|
|
28
|
+
'Addresses', 'AddressGroups', 'Regions', 'DynamicUserGroups', 'Applications',
|
|
29
|
+
'ApplicationGroups', 'ApplicationFilters', 'Services', 'ServiceGroups', 'Tags',
|
|
30
|
+
'ExternalDynamicLists', 'CustomURLCategories']
|
|
31
|
+
allowed_name_pattern = re.compile(r"[0-9a-zA-Z._-]+", re.IGNORECASE)
|
|
32
|
+
|
|
33
|
+
def __init__(self, PANDevice: Panorama | Firewall, **kwargs):
|
|
34
|
+
Base.__init__(self, PANDevice, **kwargs)
|
|
35
|
+
PAN.__init__(self, PANDevice.base_url, api_key=PANDevice.api_key)
|
|
36
|
+
self.endpoint: str = 'Objects'
|
|
37
|
+
self.disable_override: str = kwargs.get('disable-override', 'no')
|
|
38
|
+
self.has_tags: bool = kwargs.get('has_tags', False)
|
|
39
|
+
self.pan_objects: Dict[str, Any] = {}
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def disable_override(self) -> str:
|
|
43
|
+
return self._disable_override
|
|
44
|
+
|
|
45
|
+
@disable_override.setter
|
|
46
|
+
def disable_override(self, val: str) -> None:
|
|
47
|
+
# Normalize the value to lowercase if it's a string
|
|
48
|
+
val = val.lower() if isinstance(val, str) else val
|
|
49
|
+
|
|
50
|
+
# Validate the value
|
|
51
|
+
if val not in ['yes', 'no', None]:
|
|
52
|
+
raise ValueError(f"'disable_override' value must be 'yes', 'no', or None, not {val}.")
|
|
53
|
+
|
|
54
|
+
# For 'shared' location, 'disable_override' should be None
|
|
55
|
+
if self.location == 'shared':
|
|
56
|
+
self._disable_override = None
|
|
57
|
+
else:
|
|
58
|
+
# Default to 'no' if the value is None and location is not 'shared'
|
|
59
|
+
self._disable_override = val if val is not None else 'no'
|
|
60
|
+
|
|
61
|
+
# Update the entry dictionary if not in 'shared' location and if PANDevice is not a Firewall
|
|
62
|
+
if self.location != 'shared' and not isinstance(self.PANDevice, Firewall):
|
|
63
|
+
self.entry.update({'disable-override': self._disable_override})
|
|
64
|
+
|
|
65
|
+
def get(self, **kwargs) -> Union[bool, List]:
|
|
66
|
+
"""
|
|
67
|
+
Get an "Object" item from the firewall or panorama.
|
|
68
|
+
ANYLOCATION: Bool: If true, search all valid locations for the object
|
|
69
|
+
IsSearch: Bool: Don't bother logging output if we are just seearching to see if it is there.
|
|
70
|
+
:return: Object or list of all objects is name is none
|
|
71
|
+
"""
|
|
72
|
+
# Built in regions are not enumerable via the API
|
|
73
|
+
if pycountry.countries.get(alpha_2=self.name):
|
|
74
|
+
return [pycountry.countries.get(alpha_2=self.name)]
|
|
75
|
+
|
|
76
|
+
IsSearch = kwargs.get('IsSearch') or False
|
|
77
|
+
ANYLOCATION = kwargs.get('ANYLOCATION') or False
|
|
78
|
+
params = {'name': self.name,
|
|
79
|
+
'location': self.location}
|
|
80
|
+
if isinstance(self.PANDevice, Panorama):
|
|
81
|
+
if self.location != 'shared':
|
|
82
|
+
params.update({'device-group': self.loc})
|
|
83
|
+
else:
|
|
84
|
+
params.update({'vsys': self.loc})
|
|
85
|
+
|
|
86
|
+
# We can't use the PAN base class method rest_request because we need the error codes from the device
|
|
87
|
+
# to determine how we should search for a named object.
|
|
88
|
+
url = f"{self.base_url}/restapi/{self.ver}/{self.endpoint}/{self.__class__.__name__}"
|
|
89
|
+
response = self.session.request('GET', url, params=params).json()
|
|
90
|
+
|
|
91
|
+
if response.get('code') == 3:
|
|
92
|
+
if response.get('message').startswith('Invalid Query Parameter: location'):
|
|
93
|
+
# the object could be in a different location
|
|
94
|
+
for location in self.valid_location:
|
|
95
|
+
params.update({
|
|
96
|
+
'location': location,
|
|
97
|
+
})
|
|
98
|
+
response = self.rest_request('GET', params=params)
|
|
99
|
+
if response.get('@status') == 'success':
|
|
100
|
+
return response.get('result', {}).get('entry')
|
|
101
|
+
# Could not find object
|
|
102
|
+
if not IsSearch:
|
|
103
|
+
logger.error(
|
|
104
|
+
f'Could not find object {self.name} in any location on the device {self.PANDevice.hostname}')
|
|
105
|
+
return False
|
|
106
|
+
else:
|
|
107
|
+
if not IsSearch:
|
|
108
|
+
logger.error(f'Could not get object {self.name} from device {self.PANDevice.hostname}')
|
|
109
|
+
return False
|
|
110
|
+
if response.get('code') == 5:
|
|
111
|
+
if ANYLOCATION and self.location != 'shared':
|
|
112
|
+
if response.get('message') == 'Object Not Present':
|
|
113
|
+
# the object could be in a different location
|
|
114
|
+
if isinstance(self.PANDevice, Panorama):
|
|
115
|
+
# If this is a device group in Panorama, the object we are looking for can be in any
|
|
116
|
+
# parent device group within the device group hierarchy.
|
|
117
|
+
parent = self.PANDevice.device_groups_list.get(self.loc, {}).get('parent')
|
|
118
|
+
while True:
|
|
119
|
+
if parent == 'shared':
|
|
120
|
+
params.update({'location': parent})
|
|
121
|
+
if params.get('device-group'):
|
|
122
|
+
params.pop('device-group')
|
|
123
|
+
else:
|
|
124
|
+
params.update({'device-group': parent})
|
|
125
|
+
response = self.rest_request('GET', params=params)
|
|
126
|
+
if response.get('@status') == 'success':
|
|
127
|
+
logger.info(
|
|
128
|
+
f'Could not find {self.name} in location {self.loc}, however we did '
|
|
129
|
+
f'find it in {parent}.')
|
|
130
|
+
return response.get('result', {}).get('entry')
|
|
131
|
+
if parent == 'shared':
|
|
132
|
+
# There are no more parent device groups to search
|
|
133
|
+
if not IsSearch:
|
|
134
|
+
logger.warning(f'Could not find {self.name} in any device group on '
|
|
135
|
+
f'{self.PANDevice.IP}.')
|
|
136
|
+
break
|
|
137
|
+
else:
|
|
138
|
+
parent = self.PANDevice.device_groups_list.get(parent, {}).get('parent')
|
|
139
|
+
else:
|
|
140
|
+
for location in self.valid_location:
|
|
141
|
+
params.update({
|
|
142
|
+
'location': location,
|
|
143
|
+
})
|
|
144
|
+
response = self.rest_request('GET', params=params)
|
|
145
|
+
if response.get('@status') == 'success':
|
|
146
|
+
logger.info(
|
|
147
|
+
f'Could not find {self.name} in location {self.loc}, however we did '
|
|
148
|
+
f'find it in {self.location} : {location or self.loc} ')
|
|
149
|
+
return response.get('result', {}).get('entry')
|
|
150
|
+
# Could not find object
|
|
151
|
+
if not IsSearch:
|
|
152
|
+
logger.error(
|
|
153
|
+
f'Could not find object {self.name} in any location on the device {self.PANDevice.hostname}')
|
|
154
|
+
return False
|
|
155
|
+
else:
|
|
156
|
+
if not IsSearch:
|
|
157
|
+
logger.warning(
|
|
158
|
+
f'The {self.__class__.__name__} object {self.name} not found on device {self.PANDevice.hostname}')
|
|
159
|
+
return False
|
|
160
|
+
if response.get('@status') == 'success':
|
|
161
|
+
return response.get('result', {}).get('entry')
|
|
162
|
+
else:
|
|
163
|
+
if not IsSearch:
|
|
164
|
+
logger.warning(f'Could not get object {self.name} from device {self.PANDevice.hostname}')
|
|
165
|
+
return False
|
|
166
|
+
|
|
167
|
+
def refresh(self) -> bool:
|
|
168
|
+
"""
|
|
169
|
+
Retrieves live data from a device and updates the instance attributes based on the data.
|
|
170
|
+
Ensures that only one entry is returned from the data retrieval call and dynamically sets
|
|
171
|
+
the instance attributes based on the data keys, modifying them if necessary.
|
|
172
|
+
|
|
173
|
+
:return: True if the refresh is successful and instance attributes are updated, False otherwise.
|
|
174
|
+
"""
|
|
175
|
+
if not self.name:
|
|
176
|
+
logger.error('The name attribute must be available to do a refresh.')
|
|
177
|
+
return False
|
|
178
|
+
|
|
179
|
+
entry: List[Dict[str, Any]] = self.get(ANYLOCATION=True, IsSearch=True)
|
|
180
|
+
if not entry:
|
|
181
|
+
return False
|
|
182
|
+
|
|
183
|
+
if len(entry) > 1:
|
|
184
|
+
error_message: str = 'More than one entry returned; cannot refresh.'
|
|
185
|
+
logger.error(error_message)
|
|
186
|
+
raise ValueError(error_message)
|
|
187
|
+
|
|
188
|
+
updated = False
|
|
189
|
+
for key, value in entry[0].items():
|
|
190
|
+
if key == '@name':
|
|
191
|
+
setattr(self, 'name', value)
|
|
192
|
+
updated = True
|
|
193
|
+
continue
|
|
194
|
+
|
|
195
|
+
# need to replace any - with _ so it can be used as an attribute
|
|
196
|
+
modified_key: str = key.replace('-', '_')
|
|
197
|
+
# Append an underscore if the key is a Python built-in name
|
|
198
|
+
if modified_key in dir(builtins):
|
|
199
|
+
modified_key += '_'
|
|
200
|
+
|
|
201
|
+
if modified_key.startswith('@'):
|
|
202
|
+
setattr(self, modified_key.lstrip('@'), value)
|
|
203
|
+
updated = True
|
|
204
|
+
continue
|
|
205
|
+
# Check if the attribute exists in the instance before setting it
|
|
206
|
+
if hasattr(self, modified_key):
|
|
207
|
+
setattr(self, modified_key, value)
|
|
208
|
+
updated = True
|
|
209
|
+
else:
|
|
210
|
+
# If the key doesn't match any attribute, update self.entry directly
|
|
211
|
+
self.entry[key] = value
|
|
212
|
+
updated = True
|
|
213
|
+
|
|
214
|
+
return updated
|
|
215
|
+
|
|
216
|
+
def compare(self, obj2: 'ObjectTab') -> bool:
|
|
217
|
+
"""
|
|
218
|
+
Compare two objects to see if their values are the same based on CompareAttributeList.
|
|
219
|
+
"""
|
|
220
|
+
if not isinstance(obj2, type(self)):
|
|
221
|
+
raise ValueError(f'Expected object of type {type(self).__name__}, got {type(obj2).__name__} instead.')
|
|
222
|
+
|
|
223
|
+
return all(
|
|
224
|
+
getattr(self, attr, "").lower() == getattr(obj2, attr, "").lower() if isinstance(getattr(self, attr, None),
|
|
225
|
+
str)
|
|
226
|
+
else getattr(self, attr) == getattr(obj2, attr)
|
|
227
|
+
for attr in self.CompareAttributeList
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
class Addresses(Object):
|
|
232
|
+
"""
|
|
233
|
+
Manages address objects that allow you to reuse the same object as a source or destination address across all the
|
|
234
|
+
policy rulebases without having to add the address manually each time. An address object is an entity in which you
|
|
235
|
+
can include IPv4 addresses, IPv6 addresses (a single IP address, a range of addresses, or a subnet) or FQDNs.
|
|
236
|
+
"""
|
|
237
|
+
address_types = ['ip-netmask', 'ip-range', 'ip-wildcard', 'fqdn']
|
|
238
|
+
# This is a list of attributes that need to be compared to determine if 2 objects are the same.
|
|
239
|
+
CompareAttributeList = ['value'].extend(address_types)
|
|
240
|
+
|
|
241
|
+
def __init__(self, PANDevice: Panorama | Firewall, **kwargs):
|
|
242
|
+
self._ip_netmask = None
|
|
243
|
+
self._ip_range = None
|
|
244
|
+
self._ip_wildcard = None
|
|
245
|
+
self._fqdn = None
|
|
246
|
+
# Preprocess kwargs to identify and handle address type
|
|
247
|
+
addr_type_key = next((key for key in self.address_types if key in kwargs), 'ip-netmask')
|
|
248
|
+
addr_type_value = kwargs.pop(addr_type_key, None)
|
|
249
|
+
super().__init__(PANDevice, max_name_length=64, max_description_length=1024, has_tags=True, **kwargs)
|
|
250
|
+
self.value = addr_type_value if addr_type_value else ''
|
|
251
|
+
|
|
252
|
+
def validate_addr_type(self, val: str):
|
|
253
|
+
if val not in self.address_types:
|
|
254
|
+
raise ValueError(f'Invalid type. Must be one of {self.address_types}')
|
|
255
|
+
|
|
256
|
+
def validate_value(self, val: str):
|
|
257
|
+
if val:
|
|
258
|
+
try:
|
|
259
|
+
if self.AddrType in ['ip-netmask', 'ip-range']:
|
|
260
|
+
ipaddress.ip_network(val, strict=False)
|
|
261
|
+
elif self.AddrType == 'ip-wildcard':
|
|
262
|
+
# Assuming validation logic for wildcard
|
|
263
|
+
pass
|
|
264
|
+
elif self.AddrType == 'fqdn':
|
|
265
|
+
if not re.match(r"^[0-9a-zA-Z.-]{,255}$", val):
|
|
266
|
+
raise ValueError(
|
|
267
|
+
'Invalid character in name. Only [0-9a-zA-Z.-] are allowed and must be less than 255 characters.')
|
|
268
|
+
except ValueError as e:
|
|
269
|
+
raise ValueError(f'Validation error for {self.AddrType} with value {val}: {e}')
|
|
270
|
+
|
|
271
|
+
@property
|
|
272
|
+
def ip_netmask(self):
|
|
273
|
+
return self._ip_netmask
|
|
274
|
+
|
|
275
|
+
@ip_netmask.setter
|
|
276
|
+
def ip_netmask(self, value: str):
|
|
277
|
+
self.validate_ip_netmask(value)
|
|
278
|
+
self._set_address_type('ip_netmask', value)
|
|
279
|
+
|
|
280
|
+
@property
|
|
281
|
+
def ip_range(self):
|
|
282
|
+
return self._ip_range
|
|
283
|
+
|
|
284
|
+
@ip_range.setter
|
|
285
|
+
def ip_range(self, value: str):
|
|
286
|
+
self.validate_ip_range(value)
|
|
287
|
+
self._set_address_type('ip_range', value)
|
|
288
|
+
|
|
289
|
+
@property
|
|
290
|
+
def ip_wildcard(self):
|
|
291
|
+
return self._ip_wildcard
|
|
292
|
+
|
|
293
|
+
@ip_wildcard.setter
|
|
294
|
+
def ip_wildcard(self, value: str):
|
|
295
|
+
# Add validation for ip_wildcard as needed
|
|
296
|
+
self._set_address_type('ip_wildcard', value)
|
|
297
|
+
|
|
298
|
+
@property
|
|
299
|
+
def fqdn(self):
|
|
300
|
+
return self._fqdn
|
|
301
|
+
|
|
302
|
+
@fqdn.setter
|
|
303
|
+
def fqdn(self, value: str):
|
|
304
|
+
self.validate_fqdn(value)
|
|
305
|
+
self._set_address_type('fqdn', value)
|
|
306
|
+
|
|
307
|
+
def _set_address_type(self, addr_type: str, value: str):
|
|
308
|
+
"""
|
|
309
|
+
Sets the address based on the addr_type and clears other address type attributes.
|
|
310
|
+
|
|
311
|
+
:param addr_type: The type of address to set ('ip-netmask', 'ip-range', 'ip-wildcard', 'fqdn').
|
|
312
|
+
:param value: The address value to set.
|
|
313
|
+
"""
|
|
314
|
+
if addr_type == 'ip_netmask':
|
|
315
|
+
self.validate_ip_netmask(value)
|
|
316
|
+
self._clear_address_attrs()
|
|
317
|
+
setattr(self, f"_{addr_type}", value)
|
|
318
|
+
self.entry.update({'ip-netmask': value})
|
|
319
|
+
elif addr_type == 'ip_range':
|
|
320
|
+
self.validate_ip_range(value)
|
|
321
|
+
self._clear_address_attrs()
|
|
322
|
+
setattr(self, f"_{addr_type}", value)
|
|
323
|
+
self.entry.update({'ip-range': value})
|
|
324
|
+
elif addr_type == 'ip_wildcard':
|
|
325
|
+
# Add validation if necessary
|
|
326
|
+
self._clear_address_attrs()
|
|
327
|
+
setattr(self, f"_{addr_type}", value)
|
|
328
|
+
self.entry.update({'ip-wildcard': value})
|
|
329
|
+
elif addr_type == 'fqdn':
|
|
330
|
+
self.validate_fqdn(value)
|
|
331
|
+
self._clear_address_attrs()
|
|
332
|
+
setattr(self, f"_{addr_type}", value)
|
|
333
|
+
self.entry.update({'fqdn': value})
|
|
334
|
+
else:
|
|
335
|
+
raise ValueError(f"Invalid address type: {addr_type}")
|
|
336
|
+
|
|
337
|
+
def _clear_address_attrs(self):
|
|
338
|
+
"""Clears all address type attributes."""
|
|
339
|
+
for attr in ['_ip_netmask', '_ip_range', '_ip_wildcard', '_fqdn']:
|
|
340
|
+
setattr(self, attr, None)
|
|
341
|
+
for address_type in self.address_types:
|
|
342
|
+
# Remove the address type from the entry if it exists
|
|
343
|
+
self.entry.pop(address_type, None)
|
|
344
|
+
|
|
345
|
+
def validate_ip_netmask(self, value: str):
|
|
346
|
+
try:
|
|
347
|
+
ipaddress.ip_network(value, strict=False)
|
|
348
|
+
except ValueError:
|
|
349
|
+
raise ValueError(f"Invalid ip-netmask value: {value}")
|
|
350
|
+
|
|
351
|
+
def validate_ip_range(self, value: str):
|
|
352
|
+
if '-' not in value:
|
|
353
|
+
raise ValueError("IP range must contain '-' as a separator")
|
|
354
|
+
start, end = value.split('-', 1)
|
|
355
|
+
try:
|
|
356
|
+
ipaddress.ip_address(start)
|
|
357
|
+
ipaddress.ip_address(end)
|
|
358
|
+
except ValueError:
|
|
359
|
+
raise ValueError(f"Invalid IP range: {value}")
|
|
360
|
+
|
|
361
|
+
def validate_fqdn(self, value: str):
|
|
362
|
+
if not re.match(r'^[a-zA-Z\d-]{,63}(\.[a-zA-Z\d-]{,63})*$', value):
|
|
363
|
+
raise ValueError(f"Invalid FQDN: {value}")
|
|
364
|
+
|
|
365
|
+
@property
|
|
366
|
+
def value(self):
|
|
367
|
+
return self._value
|
|
368
|
+
|
|
369
|
+
@value.setter
|
|
370
|
+
def value(self, val):
|
|
371
|
+
self.validate_value(val)
|
|
372
|
+
self._value = val
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
class AddressGroups(Object):
|
|
376
|
+
"""
|
|
377
|
+
MemberObj: list of AddressObjects assigned to this list.
|
|
378
|
+
"""
|
|
379
|
+
|
|
380
|
+
valid_types = ['static', 'dynamic']
|
|
381
|
+
CompareAttributeList = ['member', 'filter'].extend(valid_types)
|
|
382
|
+
|
|
383
|
+
def __init__(self, PANDevice, **kwargs):
|
|
384
|
+
super().__init__(PANDevice, max_name_length=64, max_description_length=1024, has_tags=False, **kwargs)
|
|
385
|
+
# Initialize MemberObj list with Address objects if 'member' list is provided
|
|
386
|
+
self.MemberObj: list = []
|
|
387
|
+
self.static: Dict[str, List[str]] = {}
|
|
388
|
+
self.dynamic: Dict[str, List[str]] = {}
|
|
389
|
+
|
|
390
|
+
@property
|
|
391
|
+
def static(self):
|
|
392
|
+
return self._static
|
|
393
|
+
|
|
394
|
+
@static.setter
|
|
395
|
+
def static(self, value: dict):
|
|
396
|
+
"""
|
|
397
|
+
The setter method for the 'static' attribute.
|
|
398
|
+
Validates that the value is a dictionary containing the key 'member' with a value of type list,
|
|
399
|
+
and this list contains at least one item.
|
|
400
|
+
|
|
401
|
+
:param value: The value to set for the 'static' attribute.
|
|
402
|
+
:type value: dict
|
|
403
|
+
:raises ValueError: If the value does not meet the specified criteria.
|
|
404
|
+
"""
|
|
405
|
+
if not isinstance(value, dict):
|
|
406
|
+
raise ValueError("The 'static' attribute must be a dictionary.")
|
|
407
|
+
|
|
408
|
+
if 'member' not in value:
|
|
409
|
+
raise ValueError("The dictionary must contain the key 'member'.")
|
|
410
|
+
|
|
411
|
+
if not isinstance(value['member'], list):
|
|
412
|
+
raise ValueError("The 'member' key must have a list as its value.")
|
|
413
|
+
|
|
414
|
+
if len(value['member']) < 1:
|
|
415
|
+
raise ValueError("The list under 'member' key must contain at least one item.")
|
|
416
|
+
|
|
417
|
+
self._static = value
|
|
418
|
+
self.entry.update({'static': value})
|
|
419
|
+
|
|
420
|
+
@property
|
|
421
|
+
def dynamic(self) -> dict:
|
|
422
|
+
"""
|
|
423
|
+
The 'dynamic' property getter.
|
|
424
|
+
"""
|
|
425
|
+
return self._dynamic
|
|
426
|
+
|
|
427
|
+
@dynamic.setter
|
|
428
|
+
def dynamic(self, value: dict) -> None:
|
|
429
|
+
"""
|
|
430
|
+
The setter for the 'dynamic' attribute.
|
|
431
|
+
Validates that the value is a dictionary with a key 'filter' whose value is a string of max length 2047.
|
|
432
|
+
|
|
433
|
+
:param value: The dictionary to set as the 'dynamic' attribute.
|
|
434
|
+
:raises ValueError: If the value does not meet the specified criteria.
|
|
435
|
+
"""
|
|
436
|
+
# Check if the value is a dictionary
|
|
437
|
+
if not isinstance(value, dict):
|
|
438
|
+
raise ValueError("The 'dynamic' attribute must be a dictionary.")
|
|
439
|
+
|
|
440
|
+
# Check if the dictionary has a key named 'filter'
|
|
441
|
+
if 'filter' not in value:
|
|
442
|
+
raise ValueError("The dictionary must contain the key 'filter'.")
|
|
443
|
+
|
|
444
|
+
# Check if the value of 'filter' is a string
|
|
445
|
+
if not isinstance(value['filter'], str):
|
|
446
|
+
raise ValueError("The 'filter' key must have a string as its value.")
|
|
447
|
+
|
|
448
|
+
# Check if the string length of 'filter' is within the limit
|
|
449
|
+
if len(value['filter']) > 2047:
|
|
450
|
+
raise ValueError("The 'filter' value must not exceed 2047 characters.")
|
|
451
|
+
|
|
452
|
+
# If all checks pass, set the value
|
|
453
|
+
self._dynamic = value
|
|
454
|
+
self.entry.update({'dynamic': value})
|
|
455
|
+
|
|
456
|
+
@property
|
|
457
|
+
def MemberObj(self):
|
|
458
|
+
return self._MemberObj
|
|
459
|
+
|
|
460
|
+
@MemberObj.setter
|
|
461
|
+
def MemberObj(self, val):
|
|
462
|
+
if val is not None:
|
|
463
|
+
if not isinstance(val, list):
|
|
464
|
+
raise TypeError(f'Attribute {sys._getframe().f_code.co_name} must be of type list.')
|
|
465
|
+
self._MemberObj = val
|
|
466
|
+
|
|
467
|
+
@MemberObj.deleter
|
|
468
|
+
def MemberObj(self):
|
|
469
|
+
del self._MemberObj
|
|
470
|
+
|
|
471
|
+
def add_member(self, member):
|
|
472
|
+
if not hasattr(self, 'static'):
|
|
473
|
+
raise ValueError("Can only add members to a 'static' type AddressGroup")
|
|
474
|
+
if isinstance(member, Addresses):
|
|
475
|
+
if member.name not in self.member:
|
|
476
|
+
self.member.append(member.name)
|
|
477
|
+
self.MemberObj.append(member)
|
|
478
|
+
elif isinstance(member, str):
|
|
479
|
+
if member not in self.member:
|
|
480
|
+
self.member.append(member)
|
|
481
|
+
# Optionally, resolve the Addresses object from the name and add to MemberObj
|
|
482
|
+
else:
|
|
483
|
+
raise TypeError("Member must be an Addresses object or a string")
|
|
484
|
+
|
|
485
|
+
def remove_member(self, member):
|
|
486
|
+
if not hasattr(self, 'static'):
|
|
487
|
+
raise ValueError("Can only remove members from a 'static' type AddressGroup")
|
|
488
|
+
if isinstance(member, Addresses):
|
|
489
|
+
member_name = member.name
|
|
490
|
+
elif isinstance(member, str):
|
|
491
|
+
member_name = member
|
|
492
|
+
else:
|
|
493
|
+
raise TypeError("Member must be an Addresses object or a string")
|
|
494
|
+
|
|
495
|
+
if member_name in self.member:
|
|
496
|
+
self.member.remove(member_name)
|
|
497
|
+
self.MemberObj = [obj for obj in self.MemberObj if obj.name != member_name]
|
|
498
|
+
|
|
499
|
+
def set_filter(self, filter_str):
|
|
500
|
+
if not hasattr(self, 'dynamic'):
|
|
501
|
+
raise ValueError("Filter can only be set for a 'dynamic' type AddressGroup")
|
|
502
|
+
if len(filter_str) > 2047:
|
|
503
|
+
raise ValueError("Filter length exceeds the maximum allowed characters (2047)")
|
|
504
|
+
self.dynamic = filter_str
|
|
505
|
+
|
|
506
|
+
def get_object(self, obj_type, name: str, location: str, device_group: str, vsys: str) -> Optional[Any]:
|
|
507
|
+
"""
|
|
508
|
+
Attempt to instantiate an object of type `obj_type` with the provided parameters.
|
|
509
|
+
Returns the instantiated object if it exists, None otherwise.
|
|
510
|
+
|
|
511
|
+
:param obj_type: The class of the object to be instantiated.
|
|
512
|
+
:param name: The name of the object.
|
|
513
|
+
:param location: The location of the object.
|
|
514
|
+
:param device_group: The device group the object belongs to.
|
|
515
|
+
:param vsys: The virtual system the object belongs to.
|
|
516
|
+
:return: An instance of `obj_type` if successful, None otherwise.
|
|
517
|
+
"""
|
|
518
|
+
try:
|
|
519
|
+
obj = obj_type(PANDevice=self.PANDevice, name=name, location=location, device_group=device_group, vsys=vsys)
|
|
520
|
+
if obj.get(ANYLOCATION=True, IsSearch=True):
|
|
521
|
+
return obj
|
|
522
|
+
except Exception as e:
|
|
523
|
+
# Here you should log the exception e
|
|
524
|
+
logger.error(f"Error instantiating object of type {obj_type.__name__}: {e}")
|
|
525
|
+
return None
|
|
526
|
+
|
|
527
|
+
def populate(self) -> None:
|
|
528
|
+
"""
|
|
529
|
+
Populate the MemberObjs list with Addresses or AddressGroups from the firewall.
|
|
530
|
+
Tries to instantiate Address objects first; if that fails, tries AddressGroups.
|
|
531
|
+
Raises an exception if the static member list is empty or the objects cannot be found.
|
|
532
|
+
"""
|
|
533
|
+
members = self.static.get('member')
|
|
534
|
+
if not members:
|
|
535
|
+
raise ValueError('Cannot populate an empty group.')
|
|
536
|
+
|
|
537
|
+
for item in members:
|
|
538
|
+
new_object = self.get_object(Addresses, item, location=self.location, device_group=self.device_group,
|
|
539
|
+
vsys=self.vsys)
|
|
540
|
+
if new_object and new_object.refresh():
|
|
541
|
+
self.MemberObj.append(new_object)
|
|
542
|
+
else:
|
|
543
|
+
new_object = self.get_object(AddressGroups, item, location=self.location,
|
|
544
|
+
device_group=self.device_group, vsys=self.vsys)
|
|
545
|
+
if new_object and new_object.refresh():
|
|
546
|
+
self.MemberObj.append(new_object)
|
|
547
|
+
else:
|
|
548
|
+
logger.error(f'Could not find {item} in {self.PANDevice.IP}')
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
class Regions(Object):
|
|
552
|
+
CompareAttributeList = ['latitude', 'longitude', 'address']
|
|
553
|
+
|
|
554
|
+
def __init__(self, PANDevice, **kwargs):
|
|
555
|
+
super().__init__(PANDevice, max_name_length=32, has_tags=False, **kwargs)
|
|
556
|
+
self.geo_location = kwargs.get('geo_location')
|
|
557
|
+
self.address = kwargs.get('address')
|
|
558
|
+
|
|
559
|
+
@property
|
|
560
|
+
def geo_location(self) -> Union[None, dict]:
|
|
561
|
+
return self.entry.get('geo-location')
|
|
562
|
+
|
|
563
|
+
@geo_location.setter
|
|
564
|
+
def geo_location(self, value: Union[None, dict]):
|
|
565
|
+
if value is not None:
|
|
566
|
+
if not isinstance(value, dict):
|
|
567
|
+
raise TypeError('geo_location must be a dictionary.')
|
|
568
|
+
|
|
569
|
+
if 'latitude' in value and 'longitude' in value:
|
|
570
|
+
latitude = value['latitude']
|
|
571
|
+
longitude = value['longitude']
|
|
572
|
+
|
|
573
|
+
if not isinstance(latitude, float) or not (-90 <= latitude <= 90):
|
|
574
|
+
raise ValueError('Latitude must be a float between -90 and 90.')
|
|
575
|
+
|
|
576
|
+
if not isinstance(longitude, float) or not (-180 <= longitude <= 180):
|
|
577
|
+
raise ValueError('Longitude must be a float between -180 and 180.')
|
|
578
|
+
self._geo_location = value
|
|
579
|
+
self.entry['geo-location'] = {'latitude': latitude, 'longitude': longitude}
|
|
580
|
+
else:
|
|
581
|
+
raise ValueError('geo_location dictionary must contain both latitude and longitude keys.')
|
|
582
|
+
|
|
583
|
+
@property
|
|
584
|
+
def address(self) -> List[str]:
|
|
585
|
+
return self._address
|
|
586
|
+
|
|
587
|
+
@address.setter
|
|
588
|
+
def address(self, value: List[str]):
|
|
589
|
+
if value:
|
|
590
|
+
if not isinstance(value, list):
|
|
591
|
+
raise TypeError('The address attribute must be of type list.')
|
|
592
|
+
for addr in value:
|
|
593
|
+
if '/' in addr:
|
|
594
|
+
try:
|
|
595
|
+
ipaddress.IPv4Network(addr, strict=False)
|
|
596
|
+
except (ipaddress.AddressValueError, ipaddress.NetmaskValueError):
|
|
597
|
+
raise ValueError(f'The IP address {addr} is not valid.')
|
|
598
|
+
elif '-' in addr:
|
|
599
|
+
left_val, right_val = addr.split('-')
|
|
600
|
+
try:
|
|
601
|
+
ipaddress.IPv4Address(left_val)
|
|
602
|
+
ipaddress.IPv4Address(right_val)
|
|
603
|
+
except ipaddress.AddressValueError:
|
|
604
|
+
raise ValueError(f'The IP address range {addr} is not valid.')
|
|
605
|
+
else:
|
|
606
|
+
try:
|
|
607
|
+
ipaddress.IPv4Address(addr)
|
|
608
|
+
except ipaddress.AddressValueError:
|
|
609
|
+
raise ValueError(f'The IP address {addr} is not valid.')
|
|
610
|
+
self._address = value
|
|
611
|
+
self.entry.update({'address': {'member': value}})
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
class DynamicUserGroups(Object):
|
|
615
|
+
"""
|
|
616
|
+
Manages dynamic user groups. With dynamic user groups, you can use tags to define groups that automatically contain
|
|
617
|
+
users who match the criteria you define. These groups enable you to mitigate risk when the data about a security
|
|
618
|
+
incident contains only a username. They also allow you to link users with information from log forwarding and risk
|
|
619
|
+
assessment applications, providing a more complete view of user behavior than you would see with user directories
|
|
620
|
+
alone.
|
|
621
|
+
"""
|
|
622
|
+
CompareAttributeList = ['filter']
|
|
623
|
+
|
|
624
|
+
def __init__(self, PANDevice, **kwargs):
|
|
625
|
+
super().__init__(PANDevice, max_name_length=64, max_description_length=1024, has_tags=True,
|
|
626
|
+
**kwargs)
|
|
627
|
+
self.filter = kwargs.get('Filter')
|
|
628
|
+
|
|
629
|
+
@property
|
|
630
|
+
def filter(self) -> str:
|
|
631
|
+
return self._filter
|
|
632
|
+
|
|
633
|
+
@filter.setter
|
|
634
|
+
def filter(self, value: str) -> None:
|
|
635
|
+
if value is not None:
|
|
636
|
+
if not isinstance(value, str):
|
|
637
|
+
raise TypeError('The filter attribute must be of type str.')
|
|
638
|
+
if len(value) > 2047:
|
|
639
|
+
raise ValueError('Filter string is too long. Maximum number of characters is 2047.')
|
|
640
|
+
self._filter = value
|
|
641
|
+
if value is not None:
|
|
642
|
+
self.entry.update({'filter': value})
|
|
643
|
+
else:
|
|
644
|
+
self.entry.pop('filter', None)
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
class Applications(Object):
|
|
648
|
+
valid_types = ['port', 'ident-by-ip-protocol', 'ident-by-icmp-type', 'ident-by-icmp6-type']
|
|
649
|
+
valid_risk = [1, 2, 3, 4, 5]
|
|
650
|
+
|
|
651
|
+
# This is a list of attributes that need to be compared to determine if 2 objects are the same.
|
|
652
|
+
CompareAttributeList = ['default', 'subcategory', 'technology', 'risk']
|
|
653
|
+
|
|
654
|
+
def __init__(self, PANDevice, **kwargs):
|
|
655
|
+
super().__init__(PANDevice, max_name_length=32, max_description_length=1024, has_tags=False, **kwargs)
|
|
656
|
+
self.default: dict = kwargs.get('default')
|
|
657
|
+
self.category: str = kwargs.get('category')
|
|
658
|
+
self.subcategory: str = kwargs.get('subcategory')
|
|
659
|
+
self.technology: str = kwargs.get('technology')
|
|
660
|
+
self.timeout: int = kwargs.get('timeout')
|
|
661
|
+
self.tcp_timeout: int = kwargs.get('tcp_timeout')
|
|
662
|
+
self.udp_timeout: int = kwargs.get('udp_timeout')
|
|
663
|
+
self.tcp_half_closed_timeout: int = kwargs.get('tcp_half_closed_timeout')
|
|
664
|
+
self.tcp_time_wait_timeout: int = kwargs.get('tcp_time_wait_timeout')
|
|
665
|
+
self.risk: int = kwargs.get('risk')
|
|
666
|
+
self.evasive_behaviour: str = kwargs.get('evasive_behaviour')
|
|
667
|
+
self.consume_big_bandwidth: str = kwargs.get('consume_big_bandwidth')
|
|
668
|
+
self.used_by_malware: str = kwargs.get('used_by_malware')
|
|
669
|
+
self.able_to_transfer_file: str = kwargs.get('able_to_transfer_file')
|
|
670
|
+
self.has_known_vulnerability: str = kwargs.get('has_known_vulnerability')
|
|
671
|
+
self.tunnel_other_application: str = kwargs.get('tunnel_other_application')
|
|
672
|
+
self.tunnel_applications: str = kwargs.get('tunnel_applications')
|
|
673
|
+
self.prone_to_misuse: str = kwargs.get('prone_to_misuse')
|
|
674
|
+
self.pervasive_use: str = kwargs.get('pervasive_use')
|
|
675
|
+
self.file_type_ident: str = kwargs.get('file_type_ident')
|
|
676
|
+
self.virus_ident: str = kwargs.get('virus_ident')
|
|
677
|
+
self.data_ident: str = kwargs.get('data_ident')
|
|
678
|
+
self.no_appid_caching: str = kwargs.get('no_appid_caching')
|
|
679
|
+
self.alg_disable_capability: str = kwargs.get('alg_disable_capability', '')
|
|
680
|
+
self.parent_app: str = kwargs.get('parent_app', '')
|
|
681
|
+
self.signature = [SignatureEntry(**signature) for signature in kwargs.get('signatures', [])]
|
|
682
|
+
|
|
683
|
+
# If signatures are provided, add them to the entry dictionary
|
|
684
|
+
if 'signature' in kwargs:
|
|
685
|
+
self.entry.update({'signature': {'entry': [signature.to_dict() for signature in self.signatures]}})
|
|
686
|
+
|
|
687
|
+
def set_yes_no_attribute(self, attribute_name: str, value: str):
|
|
688
|
+
if not isinstance(value, str) or value.lower() not in ['yes', 'no']:
|
|
689
|
+
raise ValueError(f"{attribute_name} must be 'yes' or 'no'.")
|
|
690
|
+
setattr(self, f'_{attribute_name}', value.lower())
|
|
691
|
+
self.entry.update({attribute_name.replace('_', '-'): value.lower()})
|
|
692
|
+
|
|
693
|
+
@property
|
|
694
|
+
def default(self) -> dict:
|
|
695
|
+
return self._default
|
|
696
|
+
|
|
697
|
+
@default.setter
|
|
698
|
+
def default(self, value: dict):
|
|
699
|
+
if not isinstance(value, dict):
|
|
700
|
+
raise TypeError("Default must be a dictionary.")
|
|
701
|
+
|
|
702
|
+
for key, val in value.items():
|
|
703
|
+
if key not in self.valid_types:
|
|
704
|
+
raise ValueError(f"Invalid type: {key}. Must be one of: {', '.join(self.valid_types)}")
|
|
705
|
+
|
|
706
|
+
if key == 'port':
|
|
707
|
+
if not isinstance(val, dict) or 'member' not in val or not isinstance(val['member'], list):
|
|
708
|
+
raise ValueError(
|
|
709
|
+
"For 'port', the value must be a dictionary with a 'member' key containing a list of strings.")
|
|
710
|
+
if not all(isinstance(item, str) for item in val['member']):
|
|
711
|
+
raise ValueError("All items in the 'member' list for 'port' must be strings.")
|
|
712
|
+
|
|
713
|
+
elif key == 'ident-by-ip-protocol':
|
|
714
|
+
if not isinstance(val, str):
|
|
715
|
+
raise ValueError(f"The value for {key} must be a string.")
|
|
716
|
+
|
|
717
|
+
elif key in ['ident-by-icmp-type', 'ident-by-icmp6-type']:
|
|
718
|
+
if not isinstance(val, dict) or 'type' not in val or 'code' not in val:
|
|
719
|
+
raise ValueError(f"For {key}, the value must be a dictionary with 'type' and 'code' keys.")
|
|
720
|
+
if not isinstance(val['type'], str) or not isinstance(val['code'], str):
|
|
721
|
+
raise ValueError(f"Both 'type' and 'code' values for {key} must be strings.")
|
|
722
|
+
|
|
723
|
+
self._default = value
|
|
724
|
+
self.entry.update({'default': value})
|
|
725
|
+
|
|
726
|
+
@property
|
|
727
|
+
def risk(self) -> int:
|
|
728
|
+
return self._risk
|
|
729
|
+
|
|
730
|
+
@risk.setter
|
|
731
|
+
def risk(self, value: int):
|
|
732
|
+
if not isinstance(value, int) or not (1 <= value <= 5):
|
|
733
|
+
raise ValueError("Risk must be an integer between 1 and 5.")
|
|
734
|
+
self._risk = value
|
|
735
|
+
self.entry.update({'risk': value})
|
|
736
|
+
|
|
737
|
+
@property
|
|
738
|
+
def category(self) -> str:
|
|
739
|
+
return self._category
|
|
740
|
+
|
|
741
|
+
@category.setter
|
|
742
|
+
def category(self, value: str):
|
|
743
|
+
if not isinstance(value, str):
|
|
744
|
+
raise TypeError("Category must be a string.")
|
|
745
|
+
self._category = value
|
|
746
|
+
self.entry.update({'category': value})
|
|
747
|
+
|
|
748
|
+
@property
|
|
749
|
+
def subcategory(self) -> str:
|
|
750
|
+
return self._subcategory
|
|
751
|
+
|
|
752
|
+
@subcategory.setter
|
|
753
|
+
def subcategory(self, value: str):
|
|
754
|
+
if not isinstance(value, str) or len(value) > 63:
|
|
755
|
+
raise ValueError("Subcategory must be a string up to 63 characters long.")
|
|
756
|
+
self._subcategory = value
|
|
757
|
+
self.entry.update({'subcategory': value})
|
|
758
|
+
|
|
759
|
+
@property
|
|
760
|
+
def technology(self) -> str:
|
|
761
|
+
return self._technology
|
|
762
|
+
|
|
763
|
+
@technology.setter
|
|
764
|
+
def technology(self, value: str):
|
|
765
|
+
if not isinstance(value, str) or len(value) > 63:
|
|
766
|
+
raise ValueError("Technology must be a string up to 63 characters long.")
|
|
767
|
+
self._technology = value
|
|
768
|
+
self.entry.update({'technology': value})
|
|
769
|
+
|
|
770
|
+
@property
|
|
771
|
+
def evasive_behavior(self):
|
|
772
|
+
return self._evasive_behavior
|
|
773
|
+
|
|
774
|
+
@evasive_behavior.setter
|
|
775
|
+
def evasive_behavior(self, value: str):
|
|
776
|
+
self.set_yes_no_attribute('evasive_behavior', value)
|
|
777
|
+
|
|
778
|
+
@property
|
|
779
|
+
def consume_big_bandwidth(self):
|
|
780
|
+
return self._consume_big_bandwidth
|
|
781
|
+
|
|
782
|
+
@consume_big_bandwidth.setter
|
|
783
|
+
def consume_big_bandwidth(self, value: str):
|
|
784
|
+
self.set_yes_no_attribute('consume_big_bandwidth', value)
|
|
785
|
+
|
|
786
|
+
@property
|
|
787
|
+
def used_by_malware(self):
|
|
788
|
+
return self._used_by_malware
|
|
789
|
+
|
|
790
|
+
@used_by_malware.setter
|
|
791
|
+
def used_by_malware(self, value: str):
|
|
792
|
+
self.set_yes_no_attribute('used_by_malware', value)
|
|
793
|
+
|
|
794
|
+
@property
|
|
795
|
+
def able_to_transfer_file(self):
|
|
796
|
+
return self._able_to_transfer_file
|
|
797
|
+
|
|
798
|
+
@able_to_transfer_file.setter
|
|
799
|
+
def able_to_transfer_file(self, value: str):
|
|
800
|
+
self.set_yes_no_attribute('able_to_transfer_file', value)
|
|
801
|
+
|
|
802
|
+
@property
|
|
803
|
+
def has_known_vulnerability(self):
|
|
804
|
+
return self._has_known_vulnerability
|
|
805
|
+
|
|
806
|
+
@has_known_vulnerability.setter
|
|
807
|
+
def has_known_vulnerability(self, value: str):
|
|
808
|
+
self.set_yes_no_attribute('has_known_vulnerability', value)
|
|
809
|
+
|
|
810
|
+
@property
|
|
811
|
+
def tunnel_other_application(self):
|
|
812
|
+
return self._tunnel_other_application
|
|
813
|
+
|
|
814
|
+
@tunnel_other_application.setter
|
|
815
|
+
def tunnel_other_application(self, value: str):
|
|
816
|
+
self.set_yes_no_attribute('tunnel_other_application', value)
|
|
817
|
+
|
|
818
|
+
@property
|
|
819
|
+
def tunnel_applications(self):
|
|
820
|
+
return self._tunnel_applications
|
|
821
|
+
|
|
822
|
+
@tunnel_applications.setter
|
|
823
|
+
def tunnel_applications(self, value: str):
|
|
824
|
+
self.set_yes_no_attribute('tunnel_applications', value)
|
|
825
|
+
|
|
826
|
+
@property
|
|
827
|
+
def prone_to_misuse(self):
|
|
828
|
+
return self._prone_to_misuse
|
|
829
|
+
|
|
830
|
+
@prone_to_misuse.setter
|
|
831
|
+
def prone_to_misuse(self, value: str):
|
|
832
|
+
self.set_yes_no_attribute('prone_to_misuse', value)
|
|
833
|
+
|
|
834
|
+
@property
|
|
835
|
+
def pervasive_use(self):
|
|
836
|
+
return self._pervasive_use
|
|
837
|
+
|
|
838
|
+
@pervasive_use.setter
|
|
839
|
+
def pervasive_use(self, value: str):
|
|
840
|
+
self.set_yes_no_attribute('pervasive_use', value)
|
|
841
|
+
|
|
842
|
+
@property
|
|
843
|
+
def file_type_ident(self):
|
|
844
|
+
return self._file_type_ident
|
|
845
|
+
|
|
846
|
+
@file_type_ident.setter
|
|
847
|
+
def file_type_ident(self, value: str):
|
|
848
|
+
self.set_yes_no_attribute('file_type_ident', value)
|
|
849
|
+
|
|
850
|
+
@property
|
|
851
|
+
def virus_ident(self):
|
|
852
|
+
return self._virus_ident
|
|
853
|
+
|
|
854
|
+
@virus_ident.setter
|
|
855
|
+
def virus_ident(self, value: str):
|
|
856
|
+
self.set_yes_no_attribute('virus_ident', value)
|
|
857
|
+
|
|
858
|
+
@property
|
|
859
|
+
def data_ident(self):
|
|
860
|
+
return self._data_ident
|
|
861
|
+
|
|
862
|
+
@data_ident.setter
|
|
863
|
+
def data_ident(self, value: str):
|
|
864
|
+
self.set_yes_no_attribute('data_ident', value)
|
|
865
|
+
|
|
866
|
+
@property
|
|
867
|
+
def no_appid_caching(self):
|
|
868
|
+
return self._no_appid_caching
|
|
869
|
+
|
|
870
|
+
@no_appid_caching.setter
|
|
871
|
+
def no_appid_caching(self, value: str):
|
|
872
|
+
self.set_yes_no_attribute('no_appid_caching', value)
|
|
873
|
+
|
|
874
|
+
# Assuming alg_disable_capability is a string and not a yes/no field
|
|
875
|
+
@property
|
|
876
|
+
def alg_disable_capability(self):
|
|
877
|
+
return self._alg_disable_capability
|
|
878
|
+
|
|
879
|
+
@alg_disable_capability.setter
|
|
880
|
+
def alg_disable_capability(self, value: str):
|
|
881
|
+
if not isinstance(value, str) or len(value) > 127:
|
|
882
|
+
raise ValueError("alg_disable_capability must be a string up to 127 characters long.")
|
|
883
|
+
self._alg_disable_capability = value
|
|
884
|
+
self.entry.update({'alg-disable-capability': value})
|
|
885
|
+
|
|
886
|
+
# Assuming parent_app is a string and not a yes/no field
|
|
887
|
+
@property
|
|
888
|
+
def parent_app(self):
|
|
889
|
+
return self._parent_app
|
|
890
|
+
|
|
891
|
+
@parent_app.setter
|
|
892
|
+
def parent_app(self, value: str):
|
|
893
|
+
if not isinstance(value, str) or len(value) > 127:
|
|
894
|
+
raise ValueError("parent_app must be a string up to 127 characters long.")
|
|
895
|
+
self._parent_app = value
|
|
896
|
+
self.entry.update({'parent-app': value})
|
|
897
|
+
|
|
898
|
+
|
|
899
|
+
class ApplicationGroups(Object):
|
|
900
|
+
CompareAttributeList = ['members']
|
|
901
|
+
|
|
902
|
+
def __init__(self, PANDevice, **kwargs):
|
|
903
|
+
super().__init__(PANDevice, max_name_length=31, has_tags=False, **kwargs)
|
|
904
|
+
self.members: Dict[str, List[str]] = kwargs.get('members', {'member': []})
|
|
905
|
+
self.MemberObj = []
|
|
906
|
+
|
|
907
|
+
@property
|
|
908
|
+
def members(self) -> Dict[str, List[str]]:
|
|
909
|
+
return self._members
|
|
910
|
+
|
|
911
|
+
@members.setter
|
|
912
|
+
def members(self, value: Dict[str, List[str]]) -> None:
|
|
913
|
+
self.validate_member_dict(value, 'members')
|
|
914
|
+
self._members = value
|
|
915
|
+
self.entry.update({'members': value})
|
|
916
|
+
|
|
917
|
+
|
|
918
|
+
class ApplicationFilters(Object):
|
|
919
|
+
def __init__(self, PANDevice, **kwargs):
|
|
920
|
+
super().__init__(PANDevice, MaxNameLength=31, HasTags=True, **kwargs)
|
|
921
|
+
|
|
922
|
+
|
|
923
|
+
class Services(Object):
|
|
924
|
+
"""
|
|
925
|
+
Manages service objects, which are used to identify network services that applications can or can not use.
|
|
926
|
+
Network services can be defined based on protocols and/or ports. You can also use service objects to define session
|
|
927
|
+
timeout values.
|
|
928
|
+
"""
|
|
929
|
+
valid_types = ['tcp', 'udp']
|
|
930
|
+
|
|
931
|
+
# This is a list of attributes that need to be compared to determine if 2 objects are the same.
|
|
932
|
+
CompareAttributeList = ['protocol']
|
|
933
|
+
|
|
934
|
+
def __init__(self, PANDevice, **kwargs):
|
|
935
|
+
super().__init__(PANDevice, max_name_length=64, max_description_length=1024, has_tags=True, **kwargs)
|
|
936
|
+
self.protocol: Dict = kwargs.get('protocol')
|
|
937
|
+
|
|
938
|
+
@property
|
|
939
|
+
def protocol(self) -> dict:
|
|
940
|
+
return self._protocol
|
|
941
|
+
|
|
942
|
+
@protocol.setter
|
|
943
|
+
def protocol(self, value: dict) -> None:
|
|
944
|
+
if value:
|
|
945
|
+
if not isinstance(value, dict):
|
|
946
|
+
raise TypeError("Protocol must be a dictionary.")
|
|
947
|
+
|
|
948
|
+
validated_protocols = {}
|
|
949
|
+
for protocol, settings in value.items():
|
|
950
|
+
if protocol not in self.valid_types:
|
|
951
|
+
raise ValueError(f"Invalid protocol: {protocol}. Must be one of: {', '.join(self.valid_types)}")
|
|
952
|
+
|
|
953
|
+
if 'port' not in settings:
|
|
954
|
+
raise ValueError(f"'port' key is required for {protocol}.")
|
|
955
|
+
|
|
956
|
+
protocol_settings = {'port': settings['port']} # 'port' is mandatory
|
|
957
|
+
|
|
958
|
+
# Validate and include optional 'source-port'
|
|
959
|
+
if 'source-port' in settings and isinstance(settings['source-port'], str):
|
|
960
|
+
protocol_settings['source-port'] = settings['source-port']
|
|
961
|
+
|
|
962
|
+
# Initialize 'override' if it's valid
|
|
963
|
+
if 'override' in settings and isinstance(settings['override'], dict):
|
|
964
|
+
override_settings: dict = {}
|
|
965
|
+
for override_key, override_value in settings['override'].items():
|
|
966
|
+
if override_key not in ['no', 'yes']:
|
|
967
|
+
raise ValueError(f"Invalid override key: {override_key}. Must be 'no' or 'yes'.")
|
|
968
|
+
|
|
969
|
+
if override_key == 'yes' and isinstance(override_value, dict):
|
|
970
|
+
# Validate and include timeout settings
|
|
971
|
+
for timeout_key in ['timeout', 'halfclose_timeout', 'timewait_timeout']:
|
|
972
|
+
if timeout_key in override_value and isinstance(override_value[timeout_key], int):
|
|
973
|
+
if (timeout_key == 'timewait_timeout' and 1 <= override_value[
|
|
974
|
+
timeout_key] <= 600) or \
|
|
975
|
+
(timeout_key != 'timewait_timeout' and 1 <= override_value[
|
|
976
|
+
timeout_key] <= 604800):
|
|
977
|
+
# Replace underscore with dash in the timeout key
|
|
978
|
+
formatted_timeout_key = timeout_key.replace('_', '-')
|
|
979
|
+
override_settings[formatted_timeout_key] = override_value[timeout_key]
|
|
980
|
+
|
|
981
|
+
if override_settings:
|
|
982
|
+
protocol_settings['override'] = {'yes': override_settings}
|
|
983
|
+
elif override_key == 'no':
|
|
984
|
+
protocol_settings['override'] = {'no': {}}
|
|
985
|
+
|
|
986
|
+
validated_protocols[protocol] = protocol_settings
|
|
987
|
+
|
|
988
|
+
# Replace underscores with dashes for keys in self.entry
|
|
989
|
+
formatted_protocols = {k.replace('_', '-'): v for k, v in validated_protocols.items()}
|
|
990
|
+
|
|
991
|
+
self._protocol = validated_protocols
|
|
992
|
+
self.entry.update({'protocol': formatted_protocols})
|
|
993
|
+
|
|
994
|
+
class ServiceGroups(Object):
|
|
995
|
+
"""
|
|
996
|
+
Manages service groups. To simplify creation of security policies, service objects can be grouped.
|
|
997
|
+
Add service objects to a group using the object's name.
|
|
998
|
+
"""
|
|
999
|
+
CompareAttributeList = ['members']
|
|
1000
|
+
|
|
1001
|
+
def __init__(self, PANDevice, **kwargs):
|
|
1002
|
+
super().__init__(PANDevice, MaxNameLength=64, HasTags=True, **kwargs)
|
|
1003
|
+
self.members: Dict[str, List[str]] = kwargs.get('members', {'member': []})
|
|
1004
|
+
|
|
1005
|
+
@property
|
|
1006
|
+
def members(self) -> Dict[str, List[str]]:
|
|
1007
|
+
return self._members
|
|
1008
|
+
|
|
1009
|
+
@members.setter
|
|
1010
|
+
def members(self, value: Dict[str, List[str]]) -> None:
|
|
1011
|
+
self.validate_member_dict(value, 'members')
|
|
1012
|
+
self._members = value
|
|
1013
|
+
self.entry.update({'members': value})
|
|
1014
|
+
|
|
1015
|
+
class Tags(Object):
|
|
1016
|
+
valid_colors: List[str] = ['color1', 'color2', 'color3', 'color4', 'color5', 'color6', 'color7', 'color8', 'color9',
|
|
1017
|
+
'color10',
|
|
1018
|
+
'color11', 'color12', 'color13', 'color14', 'color15', 'color16', 'color17', 'color18',
|
|
1019
|
+
'color19',
|
|
1020
|
+
'color20', 'color21', 'color22', 'color23', 'color24', 'color25', 'color26', 'color27',
|
|
1021
|
+
'color28',
|
|
1022
|
+
'color29', 'color30', 'color31', 'color32', 'color33', 'color34', 'color35', 'color36',
|
|
1023
|
+
'color37',
|
|
1024
|
+
'color38', 'color39', 'color40', 'color41', 'color42']
|
|
1025
|
+
|
|
1026
|
+
CompareAttributeList = ['name']
|
|
1027
|
+
|
|
1028
|
+
def __init__(self, PANDevice, **kwargs):
|
|
1029
|
+
super().__init__(PANDevice, max_name_length=128, has_tags=False, **kwargs)
|
|
1030
|
+
self.color: str = kwargs.get('color', None)
|
|
1031
|
+
self.comments: str = kwargs.get('comments', '')
|
|
1032
|
+
|
|
1033
|
+
@property
|
|
1034
|
+
def comments(self):
|
|
1035
|
+
return self._comments
|
|
1036
|
+
|
|
1037
|
+
@comments.setter
|
|
1038
|
+
def comments(self, value: str) -> None:
|
|
1039
|
+
if not isinstance(value, str):
|
|
1040
|
+
raise TypeError(f'Attribute comments must be of type str, not {type(value).__name__}.')
|
|
1041
|
+
if len(value) > 1023:
|
|
1042
|
+
raise ValueError(
|
|
1043
|
+
f'Comment cannot exceed 1023 characters; provided comment is {len(value)} characters long.')
|
|
1044
|
+
self._comments = value
|
|
1045
|
+
self.entry.update({'comments': value})
|
|
1046
|
+
|
|
1047
|
+
@property
|
|
1048
|
+
def color(self):
|
|
1049
|
+
return self._color
|
|
1050
|
+
|
|
1051
|
+
@color.setter
|
|
1052
|
+
def color(self, value: str) -> None:
|
|
1053
|
+
if value is not None:
|
|
1054
|
+
if not isinstance(value, str):
|
|
1055
|
+
raise TypeError(f'Attribute color must be of type str, not {type(value).__name__}.')
|
|
1056
|
+
if value not in self.valid_colors:
|
|
1057
|
+
raise ValueError(f'Invalid color: {value}. Must be one of: {", ".join(self.valid_colors)}.')
|
|
1058
|
+
self._color = value
|
|
1059
|
+
self.entry.update({'color': value})
|
|
1060
|
+
else:
|
|
1061
|
+
self._color = None
|
|
1062
|
+
|
|
1063
|
+
class ExternalDynamicLists(Object):
|
|
1064
|
+
CompareAttributeList = ['type']
|
|
1065
|
+
predefined_lists = ['panw-torexit-ip-list', 'panw-bulletproof-ip-list',
|
|
1066
|
+
'panw-highrisk-ip-list', 'panw-known-ip-list',
|
|
1067
|
+
'panw-auth-portal-exclude-list']
|
|
1068
|
+
valid_types = ['predefined-ip', 'predefined-url', 'ip', 'domain', 'url', 'imsi', 'imei']
|
|
1069
|
+
|
|
1070
|
+
def __init__(self, PANDevice, **kwargs):
|
|
1071
|
+
super().__init__(PANDevice, max_name_length=64, max_description_length=256, has_tags=False,
|
|
1072
|
+
**kwargs)
|
|
1073
|
+
self.type_: Dict = kwargs.get('type', {})
|
|
1074
|
+
|
|
1075
|
+
@property
|
|
1076
|
+
def type_(self) -> Optional[Dict]:
|
|
1077
|
+
return self._type_
|
|
1078
|
+
|
|
1079
|
+
@type_.setter
|
|
1080
|
+
def type_(self, value: Dict) -> None:
|
|
1081
|
+
if not isinstance(value, dict):
|
|
1082
|
+
raise TypeError("Type must be a dictionary.")
|
|
1083
|
+
|
|
1084
|
+
for key, val in value.items():
|
|
1085
|
+
if key not in self.valid_types:
|
|
1086
|
+
raise ValueError(f"Invalid type: {key}. Must be one of: {', '.join(self.valid_types)}")
|
|
1087
|
+
|
|
1088
|
+
if key in ['predefined-ip', 'predefined-url']:
|
|
1089
|
+
self._validate_predefined_type(val, key)
|
|
1090
|
+
elif key in ['ip', 'domain', 'url', 'imsi', 'imei']:
|
|
1091
|
+
self._validate_dynamic_type(val, key)
|
|
1092
|
+
|
|
1093
|
+
self._type_ = value
|
|
1094
|
+
self.entry.update({'type': self._type_})
|
|
1095
|
+
|
|
1096
|
+
def _validate_predefined_type(self, val: Dict, type_key: str) -> None:
|
|
1097
|
+
required_keys = ['exception-list', 'description', 'url']
|
|
1098
|
+
for key in required_keys:
|
|
1099
|
+
if key not in val:
|
|
1100
|
+
raise ValueError(f"Missing '{key}' in '{type_key}' type")
|
|
1101
|
+
|
|
1102
|
+
# Add specific validation for each key as needed...
|
|
1103
|
+
|
|
1104
|
+
def _validate_dynamic_type(self, val: Dict, type_key: str) -> None:
|
|
1105
|
+
# For 'ip', 'domain', 'url', 'imsi', 'imei', only 'url' and 'recurring' are required
|
|
1106
|
+
if 'url' not in val:
|
|
1107
|
+
raise ValueError(f"'url' is required for '{type_key}' type")
|
|
1108
|
+
|
|
1109
|
+
if 'recurring' not in val:
|
|
1110
|
+
raise ValueError(f"'recurring' is required for '{type_key}' type")
|
|
1111
|
+
|
|
1112
|
+
# Validate URL
|
|
1113
|
+
if not isinstance(val['url'], str) or not re.match(r'^https?://', val['url']):
|
|
1114
|
+
raise ValueError("Invalid 'url' format.")
|
|
1115
|
+
|
|
1116
|
+
# Validate 'exception-list' if present
|
|
1117
|
+
if 'exception-list' in val:
|
|
1118
|
+
if not isinstance(val['exception-list'], dict) or 'member' not in val['exception-list']:
|
|
1119
|
+
raise ValueError("Invalid 'exception-list' format.")
|
|
1120
|
+
if not all(isinstance(item, str) for item in val['exception-list']['member']):
|
|
1121
|
+
raise ValueError("All items in 'exception-list' must be strings.")
|
|
1122
|
+
|
|
1123
|
+
# Validate 'description' if present
|
|
1124
|
+
if 'description' in val:
|
|
1125
|
+
if not isinstance(val['description'], str) or len(val['description']) > 255:
|
|
1126
|
+
raise ValueError("Invalid 'description' format.")
|
|
1127
|
+
|
|
1128
|
+
# Validate 'certificate-profile' if present
|
|
1129
|
+
if 'certificate-profile' in val:
|
|
1130
|
+
if not isinstance(val['certificate-profile'], str):
|
|
1131
|
+
raise ValueError("Invalid 'certificate-profile' format.")
|
|
1132
|
+
|
|
1133
|
+
# Validate 'auth' if present
|
|
1134
|
+
if 'auth' in val:
|
|
1135
|
+
if not isinstance(val['auth'], dict):
|
|
1136
|
+
raise ValueError("Invalid 'auth' format.")
|
|
1137
|
+
if 'username' in val['auth'] and not 1 <= len(val['auth']['username']) <= 255:
|
|
1138
|
+
raise ValueError("Invalid 'username' format.")
|
|
1139
|
+
if 'password' in val['auth'] and not isinstance(val['auth']['password'], str):
|
|
1140
|
+
raise ValueError("Invalid 'password' format.")
|
|
1141
|
+
# Validate expand-domain if present
|
|
1142
|
+
if type_key == 'domain' and 'expand-domain' in val:
|
|
1143
|
+
if val['expand-domain'] not in ['yes', 'no']:
|
|
1144
|
+
raise ValueError("Invalid 'expand-domain' value. Must be 'yes' or 'no'.")
|
|
1145
|
+
|
|
1146
|
+
# Validate recurring
|
|
1147
|
+
self._validate_recurring(val['recurring'])
|
|
1148
|
+
|
|
1149
|
+
# Add validations for optional keys if they are present...
|
|
1150
|
+
|
|
1151
|
+
@staticmethod
|
|
1152
|
+
def _validate_recurring(recurring: Dict) -> None:
|
|
1153
|
+
valid_keys = ['five-minute', 'hourly', 'daily', 'weekly', 'monthly']
|
|
1154
|
+
if not set(recurring.keys()).issubset(set(valid_keys)):
|
|
1155
|
+
raise ValueError("Invalid keys in 'recurring'.")
|
|
1156
|
+
|
|
1157
|
+
# Check that 'hourly' and 'five-minute' have empty dictionaries as values
|
|
1158
|
+
for key in ['hourly', 'five-minute']:
|
|
1159
|
+
if key in recurring and recurring[key] != {}:
|
|
1160
|
+
raise ValueError(f"'{key}' should be an empty dictionary.")
|
|
1161
|
+
|
|
1162
|
+
# 'daily' validation
|
|
1163
|
+
if 'daily' in recurring:
|
|
1164
|
+
if not isinstance(recurring['daily'], dict) or 'at' not in recurring['daily']:
|
|
1165
|
+
raise ValueError("Invalid format for 'daily' in 'recurring'.")
|
|
1166
|
+
if not isinstance(recurring['daily']['at'], str) or not recurring['daily'][
|
|
1167
|
+
'at'].isdigit() or not 0 <= int(recurring['daily']['at']) <= 23:
|
|
1168
|
+
raise ValueError("'at' in 'daily' should be a string representing an hour in the range 0-23.")
|
|
1169
|
+
|
|
1170
|
+
# 'weekly' validation
|
|
1171
|
+
if 'weekly' in recurring:
|
|
1172
|
+
if not isinstance(recurring['weekly'], dict) or 'day-of-week' not in recurring['weekly'] or 'at' not in \
|
|
1173
|
+
recurring['weekly']:
|
|
1174
|
+
raise ValueError("Invalid format for 'weekly' in 'recurring'.")
|
|
1175
|
+
if recurring['weekly']['day-of-week'].lower() not in ['sunday', 'monday', 'tuesday', 'wednesday',
|
|
1176
|
+
'thursday', 'friday', 'saturday']:
|
|
1177
|
+
raise ValueError("Invalid 'day-of-week' in 'weekly'.")
|
|
1178
|
+
if not isinstance(recurring['weekly']['at'], str) or not recurring['weekly'][
|
|
1179
|
+
'at'].isdigit() or not 0 <= int(recurring['weekly']['at']) <= 23:
|
|
1180
|
+
raise ValueError("'at' in 'weekly' should be a string representing an hour in the range 0-23.")
|
|
1181
|
+
|
|
1182
|
+
# 'monthly' validation
|
|
1183
|
+
if 'monthly' in recurring:
|
|
1184
|
+
if (not isinstance(recurring['monthly'], dict) or 'day-of-month' not in recurring['monthly'] or 'at' not in
|
|
1185
|
+
recurring['monthly']):
|
|
1186
|
+
raise ValueError("Invalid format for 'monthly' in 'recurring'.")
|
|
1187
|
+
if not isinstance(recurring['monthly']['day-of-month'], int) or not 1 <= recurring['monthly'][
|
|
1188
|
+
'day-of-month'] <= 31:
|
|
1189
|
+
raise ValueError("'day-of-month' in 'monthly' should be an integer in the range 1-31.")
|
|
1190
|
+
if not isinstance(recurring['monthly']['at'], str) or not recurring['monthly'][
|
|
1191
|
+
'at'].isdigit() or not 0 <= int(recurring['monthly']['at']) <= 23:
|
|
1192
|
+
raise ValueError("'at' in 'monthly' should be a string representing an hour in the range 0-23.")
|
|
1193
|
+
|
|
1194
|
+
class CustomURLCategories(Object):
|
|
1195
|
+
valid_types = ['URL List', 'Category Match']
|
|
1196
|
+
CompareAttributeList = ['type_', 'member']
|
|
1197
|
+
|
|
1198
|
+
def __init__(self, PANDevice, **kwargs):
|
|
1199
|
+
super().__init__(PANDevice, max_name_length=32, max_description_length=256, has_tags=False, **kwargs)
|
|
1200
|
+
self.type_: str = kwargs.get('type', 'URL List')
|
|
1201
|
+
if self.type_ not in self.valid_types:
|
|
1202
|
+
raise ValueError(f"Invalid type: {self.type_}. Must be one of: {', '.join(self.valid_types)}")
|
|
1203
|
+
self.member: Dict[List[str]] = kwargs.get('member', {'member': []})
|
|
1204
|
+
|
|
1205
|
+
@property
|
|
1206
|
+
def type_(self):
|
|
1207
|
+
return self._type_
|
|
1208
|
+
|
|
1209
|
+
@type_.setter
|
|
1210
|
+
def type_(self, value: str):
|
|
1211
|
+
if value not in self.valid_types:
|
|
1212
|
+
raise ValueError(f"Invalid type: {value}. Must be one of: {', '.join(self.valid_types)}")
|
|
1213
|
+
self._type_ = value
|
|
1214
|
+
self.entry.update({'type': value})
|
|
1215
|
+
|
|
1216
|
+
@property
|
|
1217
|
+
def member(self):
|
|
1218
|
+
return self._member
|
|
1219
|
+
|
|
1220
|
+
@member.setter
|
|
1221
|
+
def member(self, value: dict):
|
|
1222
|
+
if not isinstance(value, dict) or 'member' not in value or not isinstance(value['member'], list):
|
|
1223
|
+
raise TypeError("Member must be a dictionary with a 'member' key containing a list of strings.")
|
|
1224
|
+
for member in value['member']:
|
|
1225
|
+
if not isinstance(member, str):
|
|
1226
|
+
raise ValueError("Each member in the list must be a string.")
|
|
1227
|
+
self._member = value
|
|
1228
|
+
self.entry.update(**value)
|
|
1229
|
+
|
|
1230
|
+
def add_member(self, new_member: str):
|
|
1231
|
+
"""Add a new member to the member list."""
|
|
1232
|
+
if not isinstance(new_member, str):
|
|
1233
|
+
raise ValueError("New member must be a string.")
|
|
1234
|
+
|
|
1235
|
+
# Ensure the member is not already in the list to avoid duplicates
|
|
1236
|
+
if new_member not in self._member['member']:
|
|
1237
|
+
self._member['member'].append(new_member)
|
|
1238
|
+
self.entry.update({'member': self._member['member']})
|
|
1239
|
+
else:
|
|
1240
|
+
print(f"Member {new_member} is already in the list.")
|
|
1241
|
+
|
|
1242
|
+
def remove_member(self, member_to_remove: str):
|
|
1243
|
+
"""Remove an existing member from the member list."""
|
|
1244
|
+
if not isinstance(member_to_remove, str):
|
|
1245
|
+
raise ValueError("Member to remove must be a string.")
|
|
1246
|
+
|
|
1247
|
+
# Check if the member exists in the list before attempting to remove
|
|
1248
|
+
if member_to_remove in self._member['member']:
|
|
1249
|
+
self._member['member'].remove(member_to_remove)
|
|
1250
|
+
self.entry.update({'member': self._member['member']})
|
|
1251
|
+
else:
|
|
1252
|
+
print(f"Member {member_to_remove} not found in the list.")
|
|
1253
|
+
|
|
1254
|
+
class SDWANPathQualityProfiles(Object):
|
|
1255
|
+
def __init__(self, PANDevice, **kwargs):
|
|
1256
|
+
super().__init__(PANDevice, max_name_length=32, has_tags=False, **kwargs)
|
|
1257
|
+
self.metric: Dict = kwargs.get('metric')
|
|
1258
|
+
|
|
1259
|
+
@property
|
|
1260
|
+
def metric(self) -> Dict:
|
|
1261
|
+
return self._metric
|
|
1262
|
+
|
|
1263
|
+
@metric.setter
|
|
1264
|
+
def metric(self, value: Dict) -> None:
|
|
1265
|
+
if not isinstance(value, dict):
|
|
1266
|
+
raise TypeError("Metric must be a dictionary.")
|
|
1267
|
+
|
|
1268
|
+
valid_sensitivity = ['low', 'medium', 'high']
|
|
1269
|
+
|
|
1270
|
+
# Validate latency
|
|
1271
|
+
if 'latency' not in value or not isinstance(value['latency'], dict):
|
|
1272
|
+
raise ValueError("Latency metrics are missing or incorrect format.")
|
|
1273
|
+
latency_threshold = value['latency'].get('threshold', 100)
|
|
1274
|
+
latency_sensitivity = value['latency'].get('sensitivity', 'medium')
|
|
1275
|
+
if not (10 <= latency_threshold <= 3000):
|
|
1276
|
+
raise ValueError("Latency threshold must be between 10 and 3000.")
|
|
1277
|
+
if latency_sensitivity not in valid_sensitivity:
|
|
1278
|
+
raise ValueError("Invalid latency sensitivity value.")
|
|
1279
|
+
|
|
1280
|
+
# Validate pkt-loss
|
|
1281
|
+
if 'pkt-loss' not in value or not isinstance(value['pkt-loss'], dict):
|
|
1282
|
+
raise ValueError("Packet loss metrics are missing or incorrect format.")
|
|
1283
|
+
pkt_loss_threshold = value['pkt-loss'].get('threshold', 1)
|
|
1284
|
+
pkt_loss_sensitivity = value['pkt-loss'].get('sensitivity', 'medium')
|
|
1285
|
+
if not (1 <= pkt_loss_threshold <= 100):
|
|
1286
|
+
raise ValueError("Packet loss threshold must be between 1 and 100.")
|
|
1287
|
+
if pkt_loss_sensitivity not in valid_sensitivity:
|
|
1288
|
+
raise ValueError("Invalid packet loss sensitivity value.")
|
|
1289
|
+
|
|
1290
|
+
# Validate jitter
|
|
1291
|
+
if 'jitter' not in value or not isinstance(value['jitter'], dict):
|
|
1292
|
+
raise ValueError("Jitter metrics are missing or incorrect format.")
|
|
1293
|
+
jitter_threshold = value['jitter'].get('threshold', 100)
|
|
1294
|
+
jitter_sensitivity = value['jitter'].get('sensitivity', 'medium')
|
|
1295
|
+
if not (10 <= jitter_threshold <= 2000):
|
|
1296
|
+
raise ValueError("Jitter threshold must be between 10 and 2000.")
|
|
1297
|
+
if jitter_sensitivity not in valid_sensitivity:
|
|
1298
|
+
raise ValueError("Invalid jitter sensitivity value.")
|
|
1299
|
+
|
|
1300
|
+
# If all validations pass, set the metric
|
|
1301
|
+
self._metric = {
|
|
1302
|
+
'latency': {'threshold': latency_threshold, 'sensitivity': latency_sensitivity},
|
|
1303
|
+
'pkt-loss': {'threshold': pkt_loss_threshold, 'sensitivity': pkt_loss_sensitivity},
|
|
1304
|
+
'jitter': {'threshold': jitter_threshold, 'sensitivity': jitter_sensitivity},
|
|
1305
|
+
}
|
|
1306
|
+
self.entry.update({'metric': self._metric})
|
|
1307
|
+
|
|
1308
|
+
class GlobalProtectHIPObjects(Object):
|
|
1309
|
+
def __init__(self, PANDevice, **kwargs):
|
|
1310
|
+
super().__init__(PANDevice, max_name_length=32, max_description_lenght=255, has_tags=False, **kwargs)
|
|
1311
|
+
|
|
1312
|
+
class GlobalProtectHIPProfiles(Object):
|
|
1313
|
+
def __init__(self, PANDevice, **kwargs):
|
|
1314
|
+
super().__init__(PANDevice, max_name_length=32, max_description_lenght=255, has_tags=False, **kwargs)
|
|
1315
|
+
|
|
1316
|
+
class CustomDataPatterns(Object):
|
|
1317
|
+
def __init__(self, PANDevice, **kwargs):
|
|
1318
|
+
super().__init__(PANDevice, max_name_length=32, max_description_lenght=255, has_tags=False, **kwargs)
|
|
1319
|
+
|
|
1320
|
+
class CustomSpywareSignatures(Object):
|
|
1321
|
+
def __init__(self, PANDevice, **kwargs):
|
|
1322
|
+
super().__init__(PANDevice, has_tags=False, **kwargs)
|
|
1323
|
+
self.name: int = kwargs.get('name')
|
|
1324
|
+
|
|
1325
|
+
@property
|
|
1326
|
+
def name(self):
|
|
1327
|
+
return self._name
|
|
1328
|
+
|
|
1329
|
+
@name.setter
|
|
1330
|
+
def name(self, value: int) -> None:
|
|
1331
|
+
if 15000 <= value <= 18000 or 6900001 <= value <= 7000000:
|
|
1332
|
+
self._name = value
|
|
1333
|
+
self.entry.update({'name': value})
|
|
1334
|
+
else:
|
|
1335
|
+
raise ValueError("Value must be between 15000-18000 or 6900001-7000000")
|
|
1336
|
+
|
|
1337
|
+
class CustomVulnerabilitySignatures(Object):
|
|
1338
|
+
def __init__(self, PANDevice, **kwargs):
|
|
1339
|
+
super().__init__(PANDevice, has_tags=False, **kwargs)
|
|
1340
|
+
self.name: int = kwargs.get('name')
|
|
1341
|
+
|
|
1342
|
+
@property
|
|
1343
|
+
def name(self):
|
|
1344
|
+
return self._name
|
|
1345
|
+
|
|
1346
|
+
@name.setter
|
|
1347
|
+
def name(self, value: int) -> None:
|
|
1348
|
+
if 41000 <= value <= 45000 or 6800001 <= value <= 6900000:
|
|
1349
|
+
self._name = value
|
|
1350
|
+
self.entry.update({'name': value})
|
|
1351
|
+
else:
|
|
1352
|
+
raise ValueError("Value must be between 41000-45000 or 6800001-6900000")
|
|
1353
|
+
|
|
1354
|
+
class AntivirusSecurityProfiles(Object):
|
|
1355
|
+
def __init__(self, PANDevice, **kwargs):
|
|
1356
|
+
super().__init__(PANDevice, max_name_length=32, max_description_lenght=255, has_tags=False, **kwargs)
|
|
1357
|
+
|
|
1358
|
+
class AntiSpywareSecurityProfiles(Object):
|
|
1359
|
+
def __init__(self, PANDevice, **kwargs):
|
|
1360
|
+
super().__init__(PANDevice, max_name_length=32, max_description_lenght=255, has_tags=False, **kwargs)
|
|
1361
|
+
|
|
1362
|
+
class VulnerabilityProtectionSecurityProfiles(Object):
|
|
1363
|
+
def __init__(self, PANDevice, **kwargs):
|
|
1364
|
+
super().__init__(PANDevice, max_name_length=32, max_description_lenght=255, has_tags=False, **kwargs)
|
|
1365
|
+
|
|
1366
|
+
class URLFilteringSecurityProfiles(Object):
|
|
1367
|
+
def __init__(self, PANDevice, **kwargs):
|
|
1368
|
+
super().__init__(PANDevice, max_name_length=32, max_description_lenght=255, has_tags=False, **kwargs)
|
|
1369
|
+
|
|
1370
|
+
class FileBlockingSecurityProfiles(Object):
|
|
1371
|
+
def __init__(self, PANDevice, **kwargs):
|
|
1372
|
+
super().__init__(PANDevice, max_name_length=32, max_description_lenght=255, has_tags=False, **kwargs)
|
|
1373
|
+
|
|
1374
|
+
class WildFireAnalysisSecurityProfiles(Object):
|
|
1375
|
+
def __init__(self, PANDevice, **kwargs):
|
|
1376
|
+
super().__init__(PANDevice, max_name_length=32, max_description_lenght=255, has_tags=False, **kwargs)
|
|
1377
|
+
|
|
1378
|
+
class DataFilteringSecurityProfiles(Object):
|
|
1379
|
+
def __init__(self, PANDevice, **kwargs):
|
|
1380
|
+
super().__init__(PANDevice, max_name_length=65, max_description_lenght=255, has_tags=False, **kwargs)
|
|
1381
|
+
|
|
1382
|
+
class DoSProtectionSecurityProfiles(Object):
|
|
1383
|
+
def __init__(self, PANDevice, **kwargs):
|
|
1384
|
+
super().__init__(PANDevice, max_name_length=32, max_description_lenght=255, has_tags=False, **kwargs)
|
|
1385
|
+
|
|
1386
|
+
class GTPProtectionSecurityProfiles(Object):
|
|
1387
|
+
def __init__(self, PANDevice, **kwargs):
|
|
1388
|
+
super().__init__(PANDevice, max_name_length=64, max_description_lenght=128, has_tags=False, **kwargs)
|
|
1389
|
+
|
|
1390
|
+
class SCTPProtectionSecurityProfiles(Object):
|
|
1391
|
+
def __init__(self, PANDevice, **kwargs):
|
|
1392
|
+
super().__init__(PANDevice, max_name_length=21, max_description_lenght=255, has_tags=False, **kwargs)
|
|
1393
|
+
|
|
1394
|
+
class SecurityProfileGroups(Object):
|
|
1395
|
+
def __init__(self, PANDevice, **kwargs):
|
|
1396
|
+
super().__init__(PANDevice, max_name_length=32, has_tags=False, **kwargs)
|
|
1397
|
+
|
|
1398
|
+
class LogForwardingProfiles(Object):
|
|
1399
|
+
def __init__(self, PANDevice, **kwargs):
|
|
1400
|
+
super().__init__(PANDevice, max_name_length=65, max_description_lenght=1024, has_tags=False, **kwargs)
|
|
1401
|
+
|
|
1402
|
+
class AuthenticationEnforcements(Object):
|
|
1403
|
+
def __init__(self, PANDevice, **kwargs):
|
|
1404
|
+
super().__init__(PANDevice, max_name_length=32, has_tags=False, **kwargs)
|
|
1405
|
+
|
|
1406
|
+
class DecryptionProfiles(Object):
|
|
1407
|
+
def __init__(self, PANDevice, **kwargs):
|
|
1408
|
+
super().__init__(PANDevice, max_name_length=32, has_tags=False, **kwargs)
|
|
1409
|
+
|
|
1410
|
+
class PacketBrokerProfiles(Object):
|
|
1411
|
+
def __init__(self, PANDevice, **kwargs):
|
|
1412
|
+
super().__init__(PANDevice, max_name_length=32, max_description_lenght=255, has_tags=False, **kwargs)
|
|
1413
|
+
|
|
1414
|
+
class SDWANSaasQualityProfiles(Object):
|
|
1415
|
+
def __init__(self, PANDevice, **kwargs):
|
|
1416
|
+
super().__init__(PANDevice, max_name_length=32, has_tags=False, **kwargs)
|
|
1417
|
+
|
|
1418
|
+
class SDWANTrafficDistributionProfiles(Object):
|
|
1419
|
+
def __init__(self, PANDevice, **kwargs):
|
|
1420
|
+
super().__init__(PANDevice, max_name_length=32, has_tags=False, **kwargs)
|
|
1421
|
+
|
|
1422
|
+
class SDWANErrorCorrectionProfiles(Object):
|
|
1423
|
+
def __init__(self, PANDevice, **kwargs):
|
|
1424
|
+
super().__init__(PANDevice, max_name_length=32, has_tags=False, **kwargs)
|
|
1425
|
+
|
|
1426
|
+
class Schedules(Object):
|
|
1427
|
+
def __init__(self, PANDevice, **kwargs):
|
|
1428
|
+
super().__init__(PANDevice, max_name_length=32, has_tags=False, **kwargs)
|