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.
@@ -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)