PyPANRestV2 2.1.0__tar.gz → 2.1.2__tar.gz
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-2.1.0 → pypanrestv2-2.1.2}/PKG-INFO +45 -20
- {pypanrestv2-2.1.0 → pypanrestv2-2.1.2}/README.md +41 -18
- {pypanrestv2-2.1.0 → pypanrestv2-2.1.2}/pypanrestv2/Objects.py +73 -56
- {pypanrestv2-2.1.0 → pypanrestv2-2.1.2}/pypanrestv2/Policies.py +24 -28
- {pypanrestv2-2.1.0 → pypanrestv2-2.1.2}/pyproject.toml +1 -1
- {pypanrestv2-2.1.0 → pypanrestv2-2.1.2}/pypanrestv2/ApplicationHelper.py +0 -0
- {pypanrestv2-2.1.0 → pypanrestv2-2.1.2}/pypanrestv2/Base.py +0 -0
- {pypanrestv2-2.1.0 → pypanrestv2-2.1.2}/pypanrestv2/Device.py +0 -0
- {pypanrestv2-2.1.0 → pypanrestv2-2.1.2}/pypanrestv2/Exceptions.py +0 -0
- {pypanrestv2-2.1.0 → pypanrestv2-2.1.2}/pypanrestv2/Network.py +0 -0
- {pypanrestv2-2.1.0 → pypanrestv2-2.1.2}/pypanrestv2/Panorama.py +0 -0
- {pypanrestv2-2.1.0 → pypanrestv2-2.1.2}/pypanrestv2/XDR.py +0 -0
- {pypanrestv2-2.1.0 → pypanrestv2-2.1.2}/pypanrestv2/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: PyPANRestV2
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.2
|
|
4
4
|
Summary: Python tools for interacting with Palo Alto Networks REST API.
|
|
5
5
|
License: MIT
|
|
6
6
|
Author: Mark Rzepa
|
|
@@ -10,6 +10,8 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
10
10
|
Classifier: Programming Language :: Python :: 3
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.11
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
13
15
|
Requires-Dist: dnspython (>=2.6.1)
|
|
14
16
|
Requires-Dist: icecream (>=2.1.3)
|
|
15
17
|
Requires-Dist: pycountry (>=23.12.11)
|
|
@@ -30,14 +32,22 @@ Description-Content-Type: text/markdown
|
|
|
30
32
|
|
|
31
33
|
- **High-Level Abstraction**: Simplifies interaction with the Palo Alto Networks API.
|
|
32
34
|
- **Support for Firewalls and Panorama**: Manage both individual firewalls and Panorama devices.
|
|
33
|
-
- **REST API Integration**: Allows seamless communication with devices.
|
|
35
|
+
- **REST API Integration**: Allows seamless communication with devices using REST API.
|
|
36
|
+
- **XML API Support**: Handles XML API calls for configurations not yet available in REST API.
|
|
34
37
|
- **Convenient Pythonic Objects**: Intuitive Python objects for interacting with specific sections of Palo Alto firewall configurations.
|
|
38
|
+
- **Error Handling**: Custom exceptions for better error management and troubleshooting.
|
|
35
39
|
|
|
36
40
|
---
|
|
37
41
|
|
|
38
42
|
## Installation
|
|
39
43
|
|
|
40
|
-
|
|
44
|
+
You can install `PyPanRestV2` using pip:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install pypanrestv2
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Alternatively, you can clone the repository and install it as a package for development:
|
|
41
51
|
|
|
42
52
|
```bash
|
|
43
53
|
# Clone the repository
|
|
@@ -60,7 +70,7 @@ This will install the package and all required dependencies automatically. The `
|
|
|
60
70
|
Start by importing the necessary classes from the library:
|
|
61
71
|
|
|
62
72
|
```python
|
|
63
|
-
from
|
|
73
|
+
from pypanrestv2 import Firewall, Panorama
|
|
64
74
|
```
|
|
65
75
|
|
|
66
76
|
### Connect to a Firewall or Panorama Device
|
|
@@ -84,12 +94,12 @@ from pypanrestv2.Policies import SecurityRules
|
|
|
84
94
|
|
|
85
95
|
# Create a new security rule
|
|
86
96
|
security_rule = SecurityRules(firewall, name='allow_web')
|
|
87
|
-
security_rule.
|
|
88
|
-
security_rule.
|
|
89
|
-
security_rule.source = ['any']
|
|
90
|
-
security_rule.destination = ['any']
|
|
91
|
-
security_rule.application = ['web-browsing']
|
|
92
|
-
security_rule.service = ['application-default']
|
|
97
|
+
security_rule.from_ = {'member': ['trust']}
|
|
98
|
+
security_rule.to = {'member': ['untrust']}
|
|
99
|
+
security_rule.source = {'member': ['any']}
|
|
100
|
+
security_rule.destination = {'member': ['any']}
|
|
101
|
+
security_rule.application = {'member': ['web-browsing']}
|
|
102
|
+
security_rule.service = {'member': ['application-default']}
|
|
93
103
|
security_rule.action = 'allow'
|
|
94
104
|
security_rule.create()
|
|
95
105
|
|
|
@@ -114,16 +124,24 @@ address.create()
|
|
|
114
124
|
all_addresses = Addresses.get_all(firewall)
|
|
115
125
|
```
|
|
116
126
|
|
|
117
|
-
#### 3. Working with Panorama
|
|
127
|
+
#### 3. Working with Panorama Policies and Rulebase
|
|
118
128
|
```python
|
|
119
129
|
from pypanrestv2 import Panorama
|
|
130
|
+
from pypanrestv2.Policies import SecurityRules
|
|
120
131
|
|
|
121
132
|
# Initialize Panorama connection
|
|
122
133
|
panorama = Panorama(base_url='panorama.example.com', api_key='YOUR_API_KEY')
|
|
123
134
|
|
|
124
|
-
#
|
|
125
|
-
|
|
126
|
-
|
|
135
|
+
# Create a security rule in the pre-rulebase of a device group
|
|
136
|
+
security_rule = SecurityRules(panorama, name='allow_internal', rulebase='Pre')
|
|
137
|
+
security_rule.from_ = {'member': ['trust']}
|
|
138
|
+
security_rule.to = {'member': ['untrust']}
|
|
139
|
+
security_rule.source = {'member': ['any']}
|
|
140
|
+
security_rule.destination = {'member': ['any']}
|
|
141
|
+
security_rule.application = {'member': ['web-browsing']}
|
|
142
|
+
security_rule.service = {'member': ['application-default']}
|
|
143
|
+
security_rule.action = 'allow'
|
|
144
|
+
security_rule.create()
|
|
127
145
|
```
|
|
128
146
|
|
|
129
147
|
---
|
|
@@ -132,15 +150,22 @@ device_group.add_device('serial123')
|
|
|
132
150
|
|
|
133
151
|
Visit the project's GitHub repository for source code, documentation, enhancements, and contributions:
|
|
134
152
|
|
|
135
|
-
[PyPanRestV2 Repository on GitHub](https://github.com/
|
|
153
|
+
[PyPanRestV2 Repository on GitHub](https://github.com/mrzepa/pypanrestv2.git)
|
|
136
154
|
|
|
137
155
|
---
|
|
138
156
|
|
|
139
157
|
## Requirements
|
|
140
158
|
|
|
141
|
-
- **Python 3.11+**
|
|
142
|
-
- **Palo Alto Devices** or Panorama
|
|
143
|
-
- Python
|
|
159
|
+
- **Python 3.11+**
|
|
160
|
+
- **Palo Alto Networks Devices** running PAN-OS 9.0+ or Panorama
|
|
161
|
+
- Python dependencies:
|
|
162
|
+
- dnspython
|
|
163
|
+
- icecream
|
|
164
|
+
- pycountry
|
|
165
|
+
- python-dotenv
|
|
166
|
+
- requests
|
|
167
|
+
- tqdm
|
|
168
|
+
- validators
|
|
144
169
|
|
|
145
170
|
---
|
|
146
171
|
|
|
@@ -196,7 +221,7 @@ Be sure to check the documentation, if provided, before starting contributions.
|
|
|
196
221
|
|
|
197
222
|
## License
|
|
198
223
|
|
|
199
|
-
This project is licensed under the MIT. See the [LICENSE](
|
|
224
|
+
This project is licensed under the MIT License. See the [LICENSE](https://opensource.org/license/mit) file for details.
|
|
200
225
|
|
|
201
226
|
---
|
|
202
227
|
|
|
@@ -8,14 +8,22 @@
|
|
|
8
8
|
|
|
9
9
|
- **High-Level Abstraction**: Simplifies interaction with the Palo Alto Networks API.
|
|
10
10
|
- **Support for Firewalls and Panorama**: Manage both individual firewalls and Panorama devices.
|
|
11
|
-
- **REST API Integration**: Allows seamless communication with devices.
|
|
11
|
+
- **REST API Integration**: Allows seamless communication with devices using REST API.
|
|
12
|
+
- **XML API Support**: Handles XML API calls for configurations not yet available in REST API.
|
|
12
13
|
- **Convenient Pythonic Objects**: Intuitive Python objects for interacting with specific sections of Palo Alto firewall configurations.
|
|
14
|
+
- **Error Handling**: Custom exceptions for better error management and troubleshooting.
|
|
13
15
|
|
|
14
16
|
---
|
|
15
17
|
|
|
16
18
|
## Installation
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
You can install `PyPanRestV2` using pip:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install pypanrestv2
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Alternatively, you can clone the repository and install it as a package for development:
|
|
19
27
|
|
|
20
28
|
```bash
|
|
21
29
|
# Clone the repository
|
|
@@ -38,7 +46,7 @@ This will install the package and all required dependencies automatically. The `
|
|
|
38
46
|
Start by importing the necessary classes from the library:
|
|
39
47
|
|
|
40
48
|
```python
|
|
41
|
-
from
|
|
49
|
+
from pypanrestv2 import Firewall, Panorama
|
|
42
50
|
```
|
|
43
51
|
|
|
44
52
|
### Connect to a Firewall or Panorama Device
|
|
@@ -62,12 +70,12 @@ from pypanrestv2.Policies import SecurityRules
|
|
|
62
70
|
|
|
63
71
|
# Create a new security rule
|
|
64
72
|
security_rule = SecurityRules(firewall, name='allow_web')
|
|
65
|
-
security_rule.
|
|
66
|
-
security_rule.
|
|
67
|
-
security_rule.source = ['any']
|
|
68
|
-
security_rule.destination = ['any']
|
|
69
|
-
security_rule.application = ['web-browsing']
|
|
70
|
-
security_rule.service = ['application-default']
|
|
73
|
+
security_rule.from_ = {'member': ['trust']}
|
|
74
|
+
security_rule.to = {'member': ['untrust']}
|
|
75
|
+
security_rule.source = {'member': ['any']}
|
|
76
|
+
security_rule.destination = {'member': ['any']}
|
|
77
|
+
security_rule.application = {'member': ['web-browsing']}
|
|
78
|
+
security_rule.service = {'member': ['application-default']}
|
|
71
79
|
security_rule.action = 'allow'
|
|
72
80
|
security_rule.create()
|
|
73
81
|
|
|
@@ -92,16 +100,24 @@ address.create()
|
|
|
92
100
|
all_addresses = Addresses.get_all(firewall)
|
|
93
101
|
```
|
|
94
102
|
|
|
95
|
-
#### 3. Working with Panorama
|
|
103
|
+
#### 3. Working with Panorama Policies and Rulebase
|
|
96
104
|
```python
|
|
97
105
|
from pypanrestv2 import Panorama
|
|
106
|
+
from pypanrestv2.Policies import SecurityRules
|
|
98
107
|
|
|
99
108
|
# Initialize Panorama connection
|
|
100
109
|
panorama = Panorama(base_url='panorama.example.com', api_key='YOUR_API_KEY')
|
|
101
110
|
|
|
102
|
-
#
|
|
103
|
-
|
|
104
|
-
|
|
111
|
+
# Create a security rule in the pre-rulebase of a device group
|
|
112
|
+
security_rule = SecurityRules(panorama, name='allow_internal', rulebase='Pre')
|
|
113
|
+
security_rule.from_ = {'member': ['trust']}
|
|
114
|
+
security_rule.to = {'member': ['untrust']}
|
|
115
|
+
security_rule.source = {'member': ['any']}
|
|
116
|
+
security_rule.destination = {'member': ['any']}
|
|
117
|
+
security_rule.application = {'member': ['web-browsing']}
|
|
118
|
+
security_rule.service = {'member': ['application-default']}
|
|
119
|
+
security_rule.action = 'allow'
|
|
120
|
+
security_rule.create()
|
|
105
121
|
```
|
|
106
122
|
|
|
107
123
|
---
|
|
@@ -110,15 +126,22 @@ device_group.add_device('serial123')
|
|
|
110
126
|
|
|
111
127
|
Visit the project's GitHub repository for source code, documentation, enhancements, and contributions:
|
|
112
128
|
|
|
113
|
-
[PyPanRestV2 Repository on GitHub](https://github.com/
|
|
129
|
+
[PyPanRestV2 Repository on GitHub](https://github.com/mrzepa/pypanrestv2.git)
|
|
114
130
|
|
|
115
131
|
---
|
|
116
132
|
|
|
117
133
|
## Requirements
|
|
118
134
|
|
|
119
|
-
- **Python 3.11+**
|
|
120
|
-
- **Palo Alto Devices** or Panorama
|
|
121
|
-
- Python
|
|
135
|
+
- **Python 3.11+**
|
|
136
|
+
- **Palo Alto Networks Devices** running PAN-OS 9.0+ or Panorama
|
|
137
|
+
- Python dependencies:
|
|
138
|
+
- dnspython
|
|
139
|
+
- icecream
|
|
140
|
+
- pycountry
|
|
141
|
+
- python-dotenv
|
|
142
|
+
- requests
|
|
143
|
+
- tqdm
|
|
144
|
+
- validators
|
|
122
145
|
|
|
123
146
|
---
|
|
124
147
|
|
|
@@ -174,7 +197,7 @@ Be sure to check the documentation, if provided, before starting contributions.
|
|
|
174
197
|
|
|
175
198
|
## License
|
|
176
199
|
|
|
177
|
-
This project is licensed under the MIT. See the [LICENSE](
|
|
200
|
+
This project is licensed under the MIT License. See the [LICENSE](https://opensource.org/license/mit) file for details.
|
|
178
201
|
|
|
179
202
|
---
|
|
180
203
|
|
|
@@ -378,80 +378,96 @@ class AddressGroups(Object):
|
|
|
378
378
|
"""
|
|
379
379
|
|
|
380
380
|
valid_types = ['static', 'dynamic']
|
|
381
|
-
CompareAttributeList = ['member', 'filter']
|
|
381
|
+
CompareAttributeList = ['member', 'filter'] + valid_types
|
|
382
382
|
|
|
383
383
|
def __init__(self, PANDevice, **kwargs):
|
|
384
384
|
super().__init__(PANDevice, max_name_length=64, max_description_length=1024, has_tags=False, **kwargs)
|
|
385
|
-
# Initialize
|
|
385
|
+
# Initialize container for resolved member objects
|
|
386
386
|
self.MemberObj: list = []
|
|
387
|
-
|
|
388
|
-
|
|
387
|
+
|
|
388
|
+
# Start unset; allow instantiation without static/dynamic
|
|
389
|
+
self._static: Optional[Dict[str, List[str]]] = None
|
|
390
|
+
self._dynamic: Optional[Dict[str, str]] = None
|
|
391
|
+
|
|
392
|
+
# Optionally accept one of 'static' or 'dynamic' from kwargs
|
|
393
|
+
provided_static = kwargs.get('static')
|
|
394
|
+
provided_dynamic = kwargs.get('dynamic')
|
|
395
|
+
|
|
396
|
+
if provided_static is not None and provided_dynamic is not None:
|
|
397
|
+
raise ValueError("Only one of 'static' or 'dynamic' can be provided.")
|
|
398
|
+
|
|
399
|
+
if provided_static is not None:
|
|
400
|
+
self.static = provided_static
|
|
401
|
+
elif provided_dynamic is not None:
|
|
402
|
+
self.dynamic = provided_dynamic
|
|
389
403
|
|
|
390
404
|
@property
|
|
391
405
|
def static(self):
|
|
392
406
|
return self._static
|
|
393
407
|
|
|
394
408
|
@static.setter
|
|
395
|
-
def static(self, value: dict):
|
|
409
|
+
def static(self, value: Optional[dict]):
|
|
396
410
|
"""
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
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.
|
|
411
|
+
Set or clear the 'static' attribute.
|
|
412
|
+
When set, it must be a dict containing key 'member' with a non-empty list.
|
|
413
|
+
Setting 'static' clears 'dynamic' (mutually exclusive).
|
|
404
414
|
"""
|
|
415
|
+
if value is None:
|
|
416
|
+
self._static = None
|
|
417
|
+
# Remove from entry if present
|
|
418
|
+
self.entry.pop('static', None)
|
|
419
|
+
return
|
|
420
|
+
|
|
405
421
|
if not isinstance(value, dict):
|
|
406
422
|
raise ValueError("The 'static' attribute must be a dictionary.")
|
|
407
|
-
|
|
408
423
|
if 'member' not in value:
|
|
409
424
|
raise ValueError("The dictionary must contain the key 'member'.")
|
|
410
|
-
|
|
411
425
|
if not isinstance(value['member'], list):
|
|
412
426
|
raise ValueError("The 'member' key must have a list as its value.")
|
|
413
|
-
|
|
414
427
|
if len(value['member']) < 1:
|
|
415
428
|
raise ValueError("The list under 'member' key must contain at least one item.")
|
|
416
429
|
|
|
417
|
-
|
|
418
|
-
self.
|
|
430
|
+
# Set static and clear dynamic to ensure mutual exclusivity
|
|
431
|
+
self._static = {'member': list(value['member'])}
|
|
432
|
+
self.entry.update({'static': self._static})
|
|
433
|
+
# Clear dynamic side if set
|
|
434
|
+
self._dynamic = None
|
|
435
|
+
self.entry.pop('dynamic', None)
|
|
419
436
|
|
|
420
437
|
@property
|
|
421
|
-
def dynamic(self) -> dict:
|
|
438
|
+
def dynamic(self) -> Optional[dict]:
|
|
422
439
|
"""
|
|
423
440
|
The 'dynamic' property getter.
|
|
424
441
|
"""
|
|
425
442
|
return self._dynamic
|
|
426
443
|
|
|
427
444
|
@dynamic.setter
|
|
428
|
-
def dynamic(self, value: dict) -> None:
|
|
445
|
+
def dynamic(self, value: Optional[dict]) -> None:
|
|
429
446
|
"""
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
:param value: The dictionary to set as the 'dynamic' attribute.
|
|
434
|
-
:raises ValueError: If the value does not meet the specified criteria.
|
|
447
|
+
Set or clear the 'dynamic' attribute.
|
|
448
|
+
When set, it must be a dict with a key 'filter' whose value is a string of max length 2047.
|
|
449
|
+
Setting 'dynamic' clears 'static' (mutually exclusive).
|
|
435
450
|
"""
|
|
436
|
-
|
|
451
|
+
if value is None:
|
|
452
|
+
self._dynamic = None
|
|
453
|
+
self.entry.pop('dynamic', None)
|
|
454
|
+
return
|
|
455
|
+
|
|
437
456
|
if not isinstance(value, dict):
|
|
438
457
|
raise ValueError("The 'dynamic' attribute must be a dictionary.")
|
|
439
|
-
|
|
440
|
-
# Check if the dictionary has a key named 'filter'
|
|
441
458
|
if 'filter' not in value:
|
|
442
459
|
raise ValueError("The dictionary must contain the key 'filter'.")
|
|
443
|
-
|
|
444
|
-
# Check if the value of 'filter' is a string
|
|
445
460
|
if not isinstance(value['filter'], str):
|
|
446
461
|
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
462
|
if len(value['filter']) > 2047:
|
|
450
463
|
raise ValueError("The 'filter' value must not exceed 2047 characters.")
|
|
451
464
|
|
|
452
|
-
#
|
|
453
|
-
self._dynamic = value
|
|
454
|
-
self.entry.update({'dynamic':
|
|
465
|
+
# Set dynamic and clear static to ensure mutual exclusivity
|
|
466
|
+
self._dynamic = {'filter': value['filter']}
|
|
467
|
+
self.entry.update({'dynamic': self._dynamic})
|
|
468
|
+
# Clear static side if set
|
|
469
|
+
self._static = None
|
|
470
|
+
self.entry.pop('static', None)
|
|
455
471
|
|
|
456
472
|
@property
|
|
457
473
|
def MemberObj(self):
|
|
@@ -469,22 +485,27 @@ class AddressGroups(Object):
|
|
|
469
485
|
del self._MemberObj
|
|
470
486
|
|
|
471
487
|
def add_member(self, member):
|
|
472
|
-
|
|
488
|
+
# Ensure we are in static mode
|
|
489
|
+
if self.static is None:
|
|
473
490
|
raise ValueError("Can only add members to a 'static' type AddressGroup")
|
|
491
|
+
members = self._static.setdefault('member', [])
|
|
474
492
|
if isinstance(member, Addresses):
|
|
475
|
-
if member.name not in
|
|
476
|
-
|
|
493
|
+
if member.name not in members:
|
|
494
|
+
members.append(member.name)
|
|
477
495
|
self.MemberObj.append(member)
|
|
478
496
|
elif isinstance(member, str):
|
|
479
|
-
if member not in
|
|
480
|
-
|
|
481
|
-
# Optionally, resolve the Addresses object from the name and add to MemberObj
|
|
497
|
+
if member not in members:
|
|
498
|
+
members.append(member)
|
|
482
499
|
else:
|
|
483
500
|
raise TypeError("Member must be an Addresses object or a string")
|
|
501
|
+
# Reflect changes into entry
|
|
502
|
+
self.entry.update({'static': self._static})
|
|
484
503
|
|
|
485
504
|
def remove_member(self, member):
|
|
486
|
-
|
|
505
|
+
# Ensure we are in static mode
|
|
506
|
+
if self.static is None:
|
|
487
507
|
raise ValueError("Can only remove members from a 'static' type AddressGroup")
|
|
508
|
+
members = self._static.get('member', [])
|
|
488
509
|
if isinstance(member, Addresses):
|
|
489
510
|
member_name = member.name
|
|
490
511
|
elif isinstance(member, str):
|
|
@@ -492,35 +513,31 @@ class AddressGroups(Object):
|
|
|
492
513
|
else:
|
|
493
514
|
raise TypeError("Member must be an Addresses object or a string")
|
|
494
515
|
|
|
495
|
-
if member_name in
|
|
496
|
-
|
|
497
|
-
self.MemberObj = [obj for obj in self.MemberObj if obj
|
|
516
|
+
if member_name in members:
|
|
517
|
+
members.remove(member_name)
|
|
518
|
+
self.MemberObj = [obj for obj in self.MemberObj if getattr(obj, 'name', None) != member_name]
|
|
519
|
+
# Reflect changes into entry
|
|
520
|
+
self.entry.update({'static': self._static})
|
|
498
521
|
|
|
499
522
|
def set_filter(self, filter_str):
|
|
500
|
-
if not
|
|
501
|
-
|
|
523
|
+
# Ensure we are in dynamic mode (and set if not yet set)
|
|
524
|
+
if not isinstance(filter_str, str):
|
|
525
|
+
raise TypeError("Filter must be a string")
|
|
502
526
|
if len(filter_str) > 2047:
|
|
503
527
|
raise ValueError("Filter length exceeds the maximum allowed characters (2047)")
|
|
504
|
-
|
|
528
|
+
# This will also clear static via the setter
|
|
529
|
+
self.dynamic = {'filter': filter_str}
|
|
505
530
|
|
|
506
531
|
def get_object(self, obj_type, name: str, location: str, device_group: str, vsys: str) -> Optional[Any]:
|
|
507
532
|
"""
|
|
508
533
|
Attempt to instantiate an object of type `obj_type` with the provided parameters.
|
|
509
534
|
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
535
|
"""
|
|
518
536
|
try:
|
|
519
537
|
obj = obj_type(PANDevice=self.PANDevice, name=name, location=location, device_group=device_group, vsys=vsys)
|
|
520
538
|
if obj.get(ANYLOCATION=True, IsSearch=True):
|
|
521
539
|
return obj
|
|
522
540
|
except Exception as e:
|
|
523
|
-
# Here you should log the exception e
|
|
524
541
|
logger.error(f"Error instantiating object of type {obj_type.__name__}: {e}")
|
|
525
542
|
return None
|
|
526
543
|
|
|
@@ -530,7 +547,7 @@ class AddressGroups(Object):
|
|
|
530
547
|
Tries to instantiate Address objects first; if that fails, tries AddressGroups.
|
|
531
548
|
Raises an exception if the static member list is empty or the objects cannot be found.
|
|
532
549
|
"""
|
|
533
|
-
members = self.static.get('member')
|
|
550
|
+
members = (self.static or {}).get('member')
|
|
534
551
|
if not members:
|
|
535
552
|
raise ValueError('Cannot populate an empty group.')
|
|
536
553
|
|
|
@@ -562,12 +562,7 @@ class NatRules(Policy):
|
|
|
562
562
|
if value == 'any':
|
|
563
563
|
self._service = 'any'
|
|
564
564
|
else:
|
|
565
|
-
|
|
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
|
-
|
|
565
|
+
self._service = value
|
|
571
566
|
# Update the entry dictionary
|
|
572
567
|
self.entry.update({'service': self._service})
|
|
573
568
|
|
|
@@ -604,28 +599,29 @@ class NatRules(Policy):
|
|
|
604
599
|
|
|
605
600
|
@source_translation.setter
|
|
606
601
|
def source_translation(self, value: dict) -> None:
|
|
607
|
-
if
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
602
|
+
if value:
|
|
603
|
+
if not isinstance(value, dict):
|
|
604
|
+
raise TypeError("source_translation must be a dictionary.")
|
|
605
|
+
|
|
606
|
+
# Validate the keys of the main dict
|
|
607
|
+
valid_keys = ['dynamic-ip-and-port', 'dynamic-ip', 'static-ip']
|
|
608
|
+
if all(key not in valid_keys for key in value.keys()):
|
|
609
|
+
raise ValueError(f"Invalid key in source_translation. Must be one of: {', '.join(valid_keys)}")
|
|
610
|
+
|
|
611
|
+
keys_present = [key for key in valid_keys if key in value]
|
|
612
|
+
if len(keys_present) > 1:
|
|
613
|
+
raise ValueError("Only one of 'dynamic-ip-and-port', 'dynamic-ip', or 'static-ip' can be set at a time.")
|
|
614
|
+
|
|
615
|
+
if 'dynamic-ip-and-port' in value:
|
|
616
|
+
self._validate_dynamic_ip_and_port(value['dynamic-ip-and-port'])
|
|
617
|
+
elif 'dynamic-ip' in value:
|
|
618
|
+
self._validate_dynamic_ip(value['dynamic-ip'])
|
|
619
|
+
elif 'static-ip' in value:
|
|
620
|
+
self._validate_static_ip(value['static-ip'])
|
|
621
|
+
|
|
622
|
+
# Update the entry dictionary to reflect the change
|
|
623
|
+
self._source_translation = value
|
|
624
|
+
self.entry.update({'source-translation': self._source_translation})
|
|
629
625
|
|
|
630
626
|
def _validate_dynamic_ip_and_port(self, value: dict) -> None:
|
|
631
627
|
valid_sub_keys = ['translated-addresses', 'interface-address']
|
|
@@ -3,7 +3,7 @@ requires = ["poetry-core>=1.0.0"]
|
|
|
3
3
|
build-backend = "poetry.core.masonry.api"
|
|
4
4
|
[tool.poetry]
|
|
5
5
|
name = "PyPANRestV2"
|
|
6
|
-
version = "2.1.
|
|
6
|
+
version = "2.1.2"
|
|
7
7
|
description = "Python tools for interacting with Palo Alto Networks REST API."
|
|
8
8
|
authors = [
|
|
9
9
|
"Mark Rzepa <mark@rzepa.com>"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|