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/Panorama.py
ADDED
|
@@ -0,0 +1,426 @@
|
|
|
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
|
+
class PanoramaTab(Base, PAN):
|
|
23
|
+
def __init__(self, PANDevice, **kwargs):
|
|
24
|
+
Base.__init__(self, PANDevice, **kwargs)
|
|
25
|
+
PAN.__init__(self, PANDevice.base_url, api_key=PANDevice.api_key)
|
|
26
|
+
self.endpoint: str = 'Panorama'
|
|
27
|
+
|
|
28
|
+
def _build_params(self) -> Dict[str, str]:
|
|
29
|
+
"""
|
|
30
|
+
Builds the parameter dictionary for the API request based on the object's state.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Dict[str, str]: The parameters for the API request.
|
|
34
|
+
"""
|
|
35
|
+
params = {}
|
|
36
|
+
if self.name:
|
|
37
|
+
params['name'] = self.name
|
|
38
|
+
|
|
39
|
+
return params
|
|
40
|
+
|
|
41
|
+
class Templates(PanoramaTab):
|
|
42
|
+
def __init__(self, PANDevice, **kwargs):
|
|
43
|
+
super().__init__(PANDevice, max_description_length=255, max_name_length=63, **kwargs)
|
|
44
|
+
self.PANDevice = PANDevice
|
|
45
|
+
self.settings = kwargs.get('settings', 'vsys1')
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def settings(self):
|
|
49
|
+
return self._settings
|
|
50
|
+
|
|
51
|
+
@settings.setter
|
|
52
|
+
def settings(self, value):
|
|
53
|
+
if isinstance(value, dict):
|
|
54
|
+
if 'default-vsys' in value:
|
|
55
|
+
self._settings = value
|
|
56
|
+
self.entry.update({'settings': value})
|
|
57
|
+
return
|
|
58
|
+
elif isinstance(value, str):
|
|
59
|
+
if not value.startswith('vsys'):
|
|
60
|
+
raise ValueError(f'The attribute settings must be a vsys.')
|
|
61
|
+
self._settings = {'default-vsys': value}
|
|
62
|
+
self.entry.update({'settings': {'default-vsys': value}})
|
|
63
|
+
return
|
|
64
|
+
else:
|
|
65
|
+
raise TypeError(f'The attribute settings must be of type str, not {type(value)}.')
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class TemplateStacks(PanoramaTab):
|
|
69
|
+
variable_types = ['ip-netmask', 'ip-range', 'fqdn', 'group-id', 'device-priority', 'device-id', 'interface',
|
|
70
|
+
'as-number', 'qos-profiles', 'egress-max', 'link-tag']
|
|
71
|
+
|
|
72
|
+
def __init__(self, PANDevice, **kwargs):
|
|
73
|
+
super().__init__(PANDevice, max_description_length=255, max_name_length=63, **kwargs)
|
|
74
|
+
self.templates: Dict = {'member': []}
|
|
75
|
+
self.devices: Dict = {'entry': []}
|
|
76
|
+
self.variable: Dict = {'entry': []}
|
|
77
|
+
|
|
78
|
+
def add_device(self, name: str, variable: dict = None) -> bool:
|
|
79
|
+
"""
|
|
80
|
+
Adds a device (and its associated variable, if any) to the 'devices' entry.
|
|
81
|
+
|
|
82
|
+
This method validates the structure of the given variable (if provided), creates a new
|
|
83
|
+
device entry containing the specified name (and variable if applicable), and appends it
|
|
84
|
+
to the devices list. It ensures that the updated devices entry is assigned back to
|
|
85
|
+
the main 'entry' attribute.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
name: The name of the device to be added.
|
|
89
|
+
variable: (Optional) The data or configuration associated with the device.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
bool: True if the device was successfully added, False otherwise.
|
|
93
|
+
"""
|
|
94
|
+
# Check if a variable is provided
|
|
95
|
+
if variable:
|
|
96
|
+
if self.validate_variable_structure(variable):
|
|
97
|
+
logger.debug(f"Adding device {name} with variables to template stack {self.name}")
|
|
98
|
+
device_entry = {'@name': name, 'variable': variable}
|
|
99
|
+
else:
|
|
100
|
+
logger.debug(f"Invalid variable structure for device {name}. Not adding.")
|
|
101
|
+
logger.debug(f"Variables provided: {variable}")
|
|
102
|
+
return False
|
|
103
|
+
else:
|
|
104
|
+
# Handle case where no variables are provided
|
|
105
|
+
logger.debug(f"Adding device {name} without variables to template stack {self.name}")
|
|
106
|
+
device_entry = {'@name': name}
|
|
107
|
+
|
|
108
|
+
# Add the device entry to the devices list
|
|
109
|
+
self.devices['entry'].append(device_entry)
|
|
110
|
+
self.entry['devices'] = self.devices
|
|
111
|
+
|
|
112
|
+
return True
|
|
113
|
+
|
|
114
|
+
def update_variable(self, name: str, variable_type: str, variable_value: str):
|
|
115
|
+
if variable_type in self.variable_types:
|
|
116
|
+
variable_entry = {'@name': name, 'type': {variable_type: variable_value}}
|
|
117
|
+
self.variable['entry'].append(variable_entry)
|
|
118
|
+
self.entry['variable'] = self.variable
|
|
119
|
+
|
|
120
|
+
def update_device_variable(self, device_name: str, variable_name: str, variable_type: str,
|
|
121
|
+
variable_value: str) -> None:
|
|
122
|
+
if variable_type in self.variable_types:
|
|
123
|
+
# Find the device by name
|
|
124
|
+
for device_entry in self.devices['entry']:
|
|
125
|
+
if device_entry['@name'] == device_name:
|
|
126
|
+
# Find the variable by name within the device's 'variable' list
|
|
127
|
+
variable_found = False
|
|
128
|
+
for var_entry in device_entry['variable']['entry']:
|
|
129
|
+
if var_entry['@name'] == variable_name:
|
|
130
|
+
# Update existing variable
|
|
131
|
+
var_entry['type'] = {variable_type: variable_value}
|
|
132
|
+
variable_found = True
|
|
133
|
+
break
|
|
134
|
+
|
|
135
|
+
if not variable_found:
|
|
136
|
+
# Add new variable if not found
|
|
137
|
+
device_entry['variable']['entry'].append({
|
|
138
|
+
'@name': variable_name,
|
|
139
|
+
'type': {variable_type: variable_value}
|
|
140
|
+
})
|
|
141
|
+
break
|
|
142
|
+
|
|
143
|
+
def add_template_member(self, member):
|
|
144
|
+
self.templates['member'].append(member)
|
|
145
|
+
self.entry['templates'] = self.templates
|
|
146
|
+
|
|
147
|
+
def get_variables_from_device(self) -> list:
|
|
148
|
+
"""
|
|
149
|
+
Extracts and processes variables from a given device within the current object's devices list.
|
|
150
|
+
This function retrieves the first device from the devices list and, if that device has variables,
|
|
151
|
+
formats and appends them to a list. If no devices are present initially, it attempts to refresh the
|
|
152
|
+
device list before proceeding.
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
list: A list of dictionaries where each dictionary represents a variable with its name and type.
|
|
156
|
+
|
|
157
|
+
"""
|
|
158
|
+
def extract_variables():
|
|
159
|
+
variables = []
|
|
160
|
+
device = self.devices['entry'][0] # Fetch the first device
|
|
161
|
+
if 'variable' in device:
|
|
162
|
+
for var in device['variable']['entry']:
|
|
163
|
+
# Extract the type key and set its value to None
|
|
164
|
+
key_name = next(iter(var['type'])) # Get the first (only) key in the type dictionary
|
|
165
|
+
variables.append({
|
|
166
|
+
'@name': var['@name'],
|
|
167
|
+
'type': {key_name: None}
|
|
168
|
+
})
|
|
169
|
+
self.variable['entry'] = variables
|
|
170
|
+
return variables
|
|
171
|
+
|
|
172
|
+
if self.devices['entry']:
|
|
173
|
+
return extract_variables()
|
|
174
|
+
else:
|
|
175
|
+
self.refresh()
|
|
176
|
+
if not self.devices['entry']:
|
|
177
|
+
logger.warning(f'No devices found in {self.name} template stack.')
|
|
178
|
+
return []
|
|
179
|
+
else:
|
|
180
|
+
return extract_variables()
|
|
181
|
+
|
|
182
|
+
@property
|
|
183
|
+
def templates(self):
|
|
184
|
+
return self._templates
|
|
185
|
+
|
|
186
|
+
@templates.setter
|
|
187
|
+
def templates(self, value: Dict):
|
|
188
|
+
if self.validate_templates_structure(value):
|
|
189
|
+
self._templates = value
|
|
190
|
+
self.entry['templates'] = value
|
|
191
|
+
else:
|
|
192
|
+
raise ValueError("Invalid templates structure")
|
|
193
|
+
|
|
194
|
+
@staticmethod
|
|
195
|
+
def validate_templates_structure(templates: Dict) -> bool:
|
|
196
|
+
# Validate the structure: {'member': []}
|
|
197
|
+
if not isinstance(templates, dict) or 'member' not in templates:
|
|
198
|
+
return False
|
|
199
|
+
if not isinstance(templates['member'], list):
|
|
200
|
+
return False
|
|
201
|
+
return True
|
|
202
|
+
|
|
203
|
+
@property
|
|
204
|
+
def variable(self):
|
|
205
|
+
return self._variable
|
|
206
|
+
|
|
207
|
+
@variable.setter
|
|
208
|
+
def variable(self, value: Dict):
|
|
209
|
+
if self.validate_variable_structure(value):
|
|
210
|
+
self._variable = value
|
|
211
|
+
self.entry['variables'] = value
|
|
212
|
+
else:
|
|
213
|
+
raise ValueError("Invalid variable structure")
|
|
214
|
+
|
|
215
|
+
def validate_variable_structure(self, variable: Dict) -> bool:
|
|
216
|
+
if not isinstance(variable, dict) or 'entry' not in variable:
|
|
217
|
+
logger.debug(f'Variable is not a Dictionary or entry not in variable.')
|
|
218
|
+
return False
|
|
219
|
+
if not isinstance(variable['entry'], list):
|
|
220
|
+
logger.debug(f'Variable entry is not a list.')
|
|
221
|
+
return False
|
|
222
|
+
for item in variable['entry']:
|
|
223
|
+
if not isinstance(item, dict) or '@name' not in item or 'type' not in item:
|
|
224
|
+
logger.debug(f'Missing keys @name and type. You provided {item}')
|
|
225
|
+
return False
|
|
226
|
+
if not isinstance(item['type'], dict) or len(item['type']) != 1:
|
|
227
|
+
logger.debug(f'Key type must be a dictionary with one key. You provided {item["type"]}.')
|
|
228
|
+
return False
|
|
229
|
+
type_key = next(iter(item['type']))
|
|
230
|
+
if type_key not in self.variable_types or not isinstance(item['type'][type_key], str):
|
|
231
|
+
logger.debug(f"Key type is not valid. For variable {item['@name']}, you provided {type_key} "
|
|
232
|
+
f"as type {type(item['type'][type_key])}. Value is {item['type'][type_key]}.")
|
|
233
|
+
return False
|
|
234
|
+
return True
|
|
235
|
+
|
|
236
|
+
@property
|
|
237
|
+
def devices(self):
|
|
238
|
+
return self._devices
|
|
239
|
+
|
|
240
|
+
@devices.setter
|
|
241
|
+
def devices(self, value: Dict):
|
|
242
|
+
if self.validate_devices_structure(value):
|
|
243
|
+
self._devices = value
|
|
244
|
+
self.entry['devices'] = value
|
|
245
|
+
else:
|
|
246
|
+
raise ValueError("Invalid devices structure")
|
|
247
|
+
|
|
248
|
+
def validate_devices_structure(self, devices: Dict) -> bool:
|
|
249
|
+
if not isinstance(devices, dict) or 'entry' not in devices:
|
|
250
|
+
return False
|
|
251
|
+
if not isinstance(devices['entry'], list):
|
|
252
|
+
return False
|
|
253
|
+
for item in devices['entry']:
|
|
254
|
+
if not isinstance(item, dict) or '@name' not in item or 'variable' not in item:
|
|
255
|
+
return False
|
|
256
|
+
if not self.validate_variable_structure(item['variable']):
|
|
257
|
+
return False
|
|
258
|
+
return True
|
|
259
|
+
|
|
260
|
+
def set_variable(self, device_name, variable_name, variable_value, variable_type, variable_descriotion=None):
|
|
261
|
+
# xpath = f"/config/devices/entry[@name='localhost.localdomain']/template-stack/entry[@name='{self.name}']/devices/entry[@name='{device_name}']/variable"
|
|
262
|
+
# element = f"<entry name={variable_name}><type><{variable_type}><{variable_value}></{variable_type}></type></entry>"
|
|
263
|
+
# return self.PANDevice.set_xml(xpath, element)
|
|
264
|
+
pass
|
|
265
|
+
|
|
266
|
+
def get_template_stack(self, template_name: str) -> List[str]:
|
|
267
|
+
"""
|
|
268
|
+
Retrieves a list of template stacks containing the specified template name.
|
|
269
|
+
|
|
270
|
+
:param template_name: The name of the template to search for in template stacks.
|
|
271
|
+
:return: A list of template stacks that include the specified template.
|
|
272
|
+
"""
|
|
273
|
+
template_stacks = TemplateStacks(self.PANDevice)
|
|
274
|
+
return [
|
|
275
|
+
template_stack.get('@name')
|
|
276
|
+
for template_stack in template_stacks.get()
|
|
277
|
+
if template_name in template_stack.get('templates', {}).get('member', [])
|
|
278
|
+
]
|
|
279
|
+
|
|
280
|
+
class DeviceGroups(PanoramaTab):
|
|
281
|
+
|
|
282
|
+
def __init__(self, PANDevice, **kwargs):
|
|
283
|
+
super().__init__(PANDevice, max_description_length=255, max_name_length=63, **kwargs)
|
|
284
|
+
self.authorization_code = None
|
|
285
|
+
self.to_sw_version = 'None'
|
|
286
|
+
self.reference_templates = kwargs.get('reference_templates')
|
|
287
|
+
self.entry.update({'devices': {'entry': []}})
|
|
288
|
+
|
|
289
|
+
@property
|
|
290
|
+
def reference_templates(self):
|
|
291
|
+
return self._reference_templates
|
|
292
|
+
|
|
293
|
+
@reference_templates.setter
|
|
294
|
+
def reference_templates(self, value):
|
|
295
|
+
if value:
|
|
296
|
+
if not isinstance(value, list):
|
|
297
|
+
raise TypeError(f'Attribute {sys._getframe().f_code.co_name} must be of type list.')
|
|
298
|
+
for member in value:
|
|
299
|
+
if not isinstance(member, str):
|
|
300
|
+
raise TypeError(f'The items in {sys._getframe().f_code.co_name} list must be of type str.')
|
|
301
|
+
# Check to see if the template exists on the device
|
|
302
|
+
template = Templates(self.PANDevice, name=member)
|
|
303
|
+
if not template.get():
|
|
304
|
+
# try to see if is a Template stack instead
|
|
305
|
+
templatestack = TemplateStacks(self.PANDevice, name=member)
|
|
306
|
+
if not templatestack.get():
|
|
307
|
+
raise ValueError(f'There is no such template or template stack called {memer} on {self.PANDevice.IP}')
|
|
308
|
+
self._reference_templates = value
|
|
309
|
+
self.entry.update({'reference-templates': {'member': value}})
|
|
310
|
+
else:
|
|
311
|
+
self._reference_templates = None
|
|
312
|
+
|
|
313
|
+
@property
|
|
314
|
+
def authorization_code(self):
|
|
315
|
+
return self._authorization_code
|
|
316
|
+
|
|
317
|
+
@authorization_code.setter
|
|
318
|
+
def authorization_code(self, val):
|
|
319
|
+
if val:
|
|
320
|
+
self.Valid_Serial(val, 63)
|
|
321
|
+
self._authorization_code = val
|
|
322
|
+
self.entry.update({'authorization-code': val})
|
|
323
|
+
else:
|
|
324
|
+
self._authorization_code = None
|
|
325
|
+
|
|
326
|
+
@property
|
|
327
|
+
def to_sw_version(self):
|
|
328
|
+
return self._to_sw_version
|
|
329
|
+
|
|
330
|
+
@to_sw_version.setter
|
|
331
|
+
def to_sw_version(self, val):
|
|
332
|
+
if val:
|
|
333
|
+
if not isinstance(val, str):
|
|
334
|
+
raise TypeError(f'Attribute {sys._getframe().f_code.co_name} must be of type str.')
|
|
335
|
+
self._to_sw_version = val
|
|
336
|
+
self.entry.update({'to-sw-version': val})
|
|
337
|
+
else:
|
|
338
|
+
self._to_sw_version = 'None'
|
|
339
|
+
|
|
340
|
+
def getParentDG(self) -> Optional[str]:
|
|
341
|
+
"""
|
|
342
|
+
Returns the parent device group for a given child device group using the XML API.
|
|
343
|
+
|
|
344
|
+
Returns:
|
|
345
|
+
Optional[str]: The name of the parent device group if found, 'shared' if the device group
|
|
346
|
+
is top-level, or None if an error occurs or the parent cannot be determined.
|
|
347
|
+
"""
|
|
348
|
+
URL = f'{self.PANDevice.base_url}/api/'
|
|
349
|
+
xpath = f'/config/readonly/devices/entry[@name="localhost.localdomain"]/device-group/entry[@name="{self.name}"]/parent-dg'
|
|
350
|
+
params = {
|
|
351
|
+
'type': 'config',
|
|
352
|
+
'action': 'get',
|
|
353
|
+
'xpath': xpath,
|
|
354
|
+
'key': self.PANDevice.API_KEY # Assuming API_KEY is required for authentication
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
try:
|
|
358
|
+
response = self.session.get(URL, params=params)
|
|
359
|
+
response.raise_for_status() # Check for HTTP errors
|
|
360
|
+
|
|
361
|
+
result = xmltodict.parse(response.text)
|
|
362
|
+
status = result.get('response', {}).get('@status')
|
|
363
|
+
|
|
364
|
+
if status == 'success':
|
|
365
|
+
parent_dg = result.get('response', {}).get('result', {}).get('parent-dg')
|
|
366
|
+
return parent_dg if parent_dg is not None else 'shared'
|
|
367
|
+
else:
|
|
368
|
+
logger.error(f'Could not get parent DG for {self.name}: {result.get("response", {}).get("msg")}')
|
|
369
|
+
return None
|
|
370
|
+
except requests.exceptions.RequestException as e:
|
|
371
|
+
logger.error(f'HTTP error occurred: {e}')
|
|
372
|
+
except Exception as e:
|
|
373
|
+
logger.error(f'Error parsing response: {e}')
|
|
374
|
+
return None
|
|
375
|
+
|
|
376
|
+
def add_device(self, serial: str) -> None:
|
|
377
|
+
"""
|
|
378
|
+
Adds a device by serial number to the device group.
|
|
379
|
+
|
|
380
|
+
Parameters:
|
|
381
|
+
- serial (str): Serial number of the firewall to add to this group.
|
|
382
|
+
|
|
383
|
+
Raises:
|
|
384
|
+
- ValueError: If the serial number is invalid or empty.
|
|
385
|
+
- AttributeError: If the devices list or entry dictionary is not properly initialized.
|
|
386
|
+
"""
|
|
387
|
+
if not serial:
|
|
388
|
+
raise ValueError("Serial number cannot be empty.")
|
|
389
|
+
|
|
390
|
+
try:
|
|
391
|
+
# Ensure 'entry' is a dictionary as expected.
|
|
392
|
+
if 'devices' not in self.entry or 'entry' not in self.entry['devices'] or not isinstance(
|
|
393
|
+
self.entry['devices']['entry'], list):
|
|
394
|
+
raise AttributeError("'entry' dictionary is not properly initialized.")
|
|
395
|
+
|
|
396
|
+
# Add the serial number to the devices list and entry dictionary.
|
|
397
|
+
self.entry['devices']['entry'].append({'@name': serial})
|
|
398
|
+
except AttributeError as e:
|
|
399
|
+
logger.error(f"Failed to add device: {e}")
|
|
400
|
+
|
|
401
|
+
def add_reference_template(self, template_name: str) -> None:
|
|
402
|
+
"""
|
|
403
|
+
Adds a new reference template to the device group.
|
|
404
|
+
|
|
405
|
+
Parameters:
|
|
406
|
+
- template_name (str): The name of the template to add.
|
|
407
|
+
|
|
408
|
+
Raises:
|
|
409
|
+
- ValueError: If the template_name is empty.
|
|
410
|
+
- TypeError: If template_name is not a string.
|
|
411
|
+
"""
|
|
412
|
+
if not isinstance(template_name, str):
|
|
413
|
+
raise TypeError("template_name must be a string.")
|
|
414
|
+
if not template_name:
|
|
415
|
+
raise ValueError("template_name cannot be empty.")
|
|
416
|
+
|
|
417
|
+
# Initialize the 'reference-templates' structure if it doesn't exist
|
|
418
|
+
if 'reference-templates' not in self.entry:
|
|
419
|
+
self.entry['reference-templates'] = {'member': []}
|
|
420
|
+
|
|
421
|
+
# Check to ensure the template_name does not already exist to prevent duplicates
|
|
422
|
+
if template_name not in self.entry['reference-templates']['member']:
|
|
423
|
+
self.entry['reference-templates']['member'].append(template_name)
|
|
424
|
+
else:
|
|
425
|
+
# Log or handle the case where the template already exists if needed
|
|
426
|
+
logger.warning(f"Template '{template_name}' already exists in the reference templates.")
|