├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.md ├── requirements.txt ├── setup.py └── sonicapi ├── __init__.py ├── __version__.py └── sonicapi.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | sonicapi/__pycache__ 3 | .vscode 4 | test.py 5 | build/ 6 | dist/ 7 | sonicapi.egg-info/ 8 | .bumpversion.cfg 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021-2025, Henry Bonath (henry@thebonaths.com) 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of the copyright holder nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md LICENSE -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sonicapi 2 | ## Python3 Module to interact with the SonicWall® SonicOS API 3 | 4 | 5 | > This Module currently only contains basic functionality: 6 | > 7 | >* Address Objects 8 | >* Address Groups 9 | >* Service Objects 10 | >* Service Groups 11 | >* Zones 12 | >* Access Rules 13 | >* NAT Policies 14 | >* Route Policies 15 | >* Restart 16 | >* VPN Policies 17 | 18 | ### Installation: 19 | 20 | ``` 21 | pip3 install sonicapi 22 | ``` 23 | 24 | ### Usage: 25 | 26 | ``` 27 | from sonicapi import sonicapi 28 | import json 29 | 30 | def main(): 31 | # This example connects to the API, dumps out a JSON list of Address Objects, and logs out. 32 | s = sonicapi('192.168.168.168', 443, 'admin', 'password') 33 | print(json.dumps(s.auth(login=True), indent=2)) 34 | print(json.dumps(s.AddressObjects(type='ipv4'), indent=2)) 35 | print(json.dumps(s.AddressObjects(type='ipv6'), indent=2)) 36 | print(json.dumps(s.auth(logout=True), indent=2)) 37 | 38 | 39 | if __name__ == "__main__": 40 | main() 41 | ``` 42 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests>=2.25.1 2 | urllib3 3 | paramiko 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import io 5 | import os 6 | import sys 7 | from shutil import rmtree 8 | 9 | from setuptools import find_packages, setup, Command 10 | 11 | # Package meta-data. 12 | NAME = 'sonicapi' 13 | DESCRIPTION = 'Python3 Module to interact with the SonicWall® SonicOS API.' 14 | URL = 'https://github.com/hbonath/sonicapi' 15 | EMAIL = 'henry@thebonaths.com' 16 | AUTHOR = 'Henry Bonath' 17 | REQUIRES_PYTHON = '>=3.6.0' 18 | VERSION = '0.2.5' 19 | 20 | REQUIRED = [ 21 | 'requests>=2.25.1', 22 | 'urllib3>=1.25.8' 23 | ] 24 | 25 | EXTRAS = {} 26 | 27 | here = os.path.abspath(os.path.dirname(__file__)) 28 | 29 | try: 30 | with io.open(os.path.join(here, 'README.md'), encoding='utf-8') as f: 31 | long_description = '\n' + f.read() 32 | except FileNotFoundError: 33 | long_description = DESCRIPTION 34 | 35 | about = {} 36 | if not VERSION: 37 | project_slug = NAME.lower().replace("-", "_").replace(" ", "_") 38 | with open(os.path.join(here, project_slug, '__version__.py')) as f: 39 | exec(f.read(), about) 40 | else: 41 | about['__version__'] = VERSION 42 | 43 | 44 | class UploadCommand(Command): 45 | """Support setup.py upload.""" 46 | 47 | description = 'Build and publish the package.' 48 | user_options = [] 49 | 50 | @staticmethod 51 | def status(s): 52 | """Prints things in bold.""" 53 | print('\033[1m{0}\033[0m'.format(s)) 54 | 55 | def initialize_options(self): 56 | pass 57 | 58 | def finalize_options(self): 59 | pass 60 | 61 | def run(self): 62 | try: 63 | self.status('Removing previous builds…') 64 | rmtree(os.path.join(here, 'dist')) 65 | except OSError: 66 | pass 67 | 68 | self.status('Building Source and Wheel (universal) distribution…') 69 | os.system('{0} setup.py sdist bdist_wheel --universal'.format(sys.executable)) 70 | 71 | self.status('Uploading the package to PyPI via Twine…') 72 | os.system('twine upload dist/*') 73 | 74 | self.status('Pushing git tags…') 75 | os.system('git tag v{0}'.format(about['__version__'])) 76 | os.system('git push --tags') 77 | 78 | sys.exit() 79 | 80 | 81 | # Where the magic happens: 82 | setup( 83 | name=NAME, 84 | version=about['__version__'], 85 | description=DESCRIPTION, 86 | long_description=long_description, 87 | long_description_content_type='text/markdown', 88 | author=AUTHOR, 89 | author_email=EMAIL, 90 | python_requires=REQUIRES_PYTHON, 91 | url=URL, 92 | packages=find_packages(exclude=["tests", "*.tests", "*.tests.*", "tests.*"]), 93 | # If your package is a single module, use this instead of 'packages': 94 | # py_modules=['sonicapi'], 95 | 96 | # entry_points={ 97 | # 'console_scripts': ['mycli=mymodule:cli'], 98 | # }, 99 | install_requires=REQUIRED, 100 | extras_require=EXTRAS, 101 | include_package_data=True, 102 | license='BSD', 103 | classifiers=[ 104 | # Trove classifiers 105 | # Full list: https://pypi.python.org/pypi?%3Aaction=list_classifiers 106 | 'License :: OSI Approved :: BSD License', 107 | 'Programming Language :: Python', 108 | 'Programming Language :: Python :: 3', 109 | 'Programming Language :: Python :: 3.6', 110 | 'Programming Language :: Python :: Implementation :: CPython', 111 | 'Programming Language :: Python :: Implementation :: PyPy' 112 | ], 113 | # $ setup.py publish support. 114 | cmdclass={ 115 | 'upload': UploadCommand, 116 | }, 117 | ) 118 | -------------------------------------------------------------------------------- /sonicapi/__init__.py: -------------------------------------------------------------------------------- 1 | # __init__.py 2 | from .sonicapi import * 3 | -------------------------------------------------------------------------------- /sonicapi/__version__.py: -------------------------------------------------------------------------------- 1 | """ 2 | ██ ██ 3 | ▀▀ ▀▀ 4 | ▄▄█████▄ ▄████▄ ██▄████▄ ████ ▄█████▄ ▄█████▄ ██▄███▄ ████ 5 | ██▄▄▄▄ ▀ ██▀ ▀██ ██▀ ██ ██ ██▀ ▀ ▀ ▄▄▄██ ██▀ ▀██ ██ 6 | ▀▀▀▀██▄ ██ ██ ██ ██ ██ ██ ▄██▀▀▀██ ██ ██ ██ 7 | █▄▄▄▄▄██ ▀██▄▄██▀ ██ ██ ▄▄▄██▄▄▄ ▀██▄▄▄▄█ ██▄▄▄███ ███▄▄██▀ ▄▄▄██▄▄▄ 8 | ▀▀▀▀▀▀ ▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀▀▀▀▀ ▀▀▀▀▀ ▀▀▀▀ ▀▀ ██ ▀▀▀ ▀▀▀▀▀▀▀▀ 9 | ██ 10 | """ 11 | VERSION = (0, 2, 1) 12 | 13 | __version__ = '.'.join(map(str, VERSION)) 14 | -------------------------------------------------------------------------------- /sonicapi/sonicapi.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # 4 | # Copyright (c) 2021-2025, Henry Bonath (henry@thebonaths.com) 5 | # All rights reserved. 6 | # 7 | # Redistribution and use in source and binary forms, with or without 8 | # modification, are permitted provided that the following conditions are met: 9 | # 10 | # 1. Redistributions of source code must retain the above copyright notice, this 11 | # list of conditions and the following disclaimer. 12 | # 13 | # 2. Redistributions in binary form must reproduce the above copyright notice, 14 | # this list of conditions and the following disclaimer in the documentation 15 | # and/or other materials provided with the distribution. 16 | # 17 | # 3. Neither the name of the copyright holder nor the names of its 18 | # contributors may be used to endorse or promote products derived from 19 | # this software without specific prior written permission. 20 | # 21 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | 32 | import json 33 | from collections import OrderedDict 34 | from paramiko import PasswordRequiredException 35 | import requests 36 | import urllib3 37 | from requests.auth import HTTPDigestAuth 38 | urllib3.disable_warnings() 39 | 40 | 41 | class sonicapi: 42 | """ 43 | A class represting a connection to a SonicWALL Appliance. 44 | 45 | Attributes: 46 | ----------- 47 | hostname : str 48 | IP Address or Hostname of Appliance 49 | port : int 50 | TCP Port used for HTTPS Management 51 | username : str 52 | Username of admin-level user 53 | password : str 54 | Password of admin-level user 55 | 56 | Methods: 57 | -------- 58 | auth 59 | AddressObjects 60 | AddressGroups 61 | ServiceObjects 62 | ServiceGroups 63 | Zones 64 | NatPolicies 65 | AccessRules 66 | RoutePolicies 67 | Restart 68 | VpnPolicy 69 | """ 70 | def __init__(self, hostname, port, username, password): 71 | self.baseurl = 'https://{}:{}/api/sonicos/'.format( 72 | hostname, str(port)) 73 | self.authinfo = (username, password) 74 | self.headers = OrderedDict([ 75 | ('Accept', 'application/json'), 76 | ('Content-Type', 'application/json'), 77 | ('Accept-Encoding', 'application/json'), 78 | ('Charset', 'UTF-8')]) 79 | self.kwargs = { 80 | 'auth': self.authinfo, 81 | 'headers': self.headers, 82 | 'verify': False, 83 | } 84 | self.response = { 85 | 'status': { 86 | 'success': None, 87 | 'info': [{ 88 | 'level': None, 89 | 'code': None, 90 | 'message': None 91 | }] 92 | }, 93 | } 94 | 95 | def _api_head(self, controller, data=None): 96 | """ 97 | Internal method used to send HTTP HEAD 98 | """ 99 | uri = controller 100 | url = self.baseurl + uri 101 | if data is not None: 102 | try: 103 | jsondata = json.dumps(data) 104 | except: 105 | jsondata = json.dumps({}) 106 | r = requests.head(url, data=jsondata, **self.kwargs) 107 | else: 108 | r = requests.head(url, **self.kwargs) 109 | response = r.json() 110 | return response 111 | 112 | def _api_get(self, controller): 113 | """ 114 | Internal method used to send HTTP GET 115 | """ 116 | uri = controller 117 | url = self.baseurl + uri 118 | r = requests.get(url, **self.kwargs) 119 | response = r.json() 120 | return response 121 | 122 | def _api_post(self, controller, data=None): 123 | """ 124 | Internal method used to send HTTP POST 125 | """ 126 | uri = controller 127 | url = self.baseurl + uri 128 | if data is not None: 129 | try: 130 | jsondata = json.dumps(data) 131 | except: 132 | jsondata = json.dumps({}) 133 | r = requests.post(url, data=jsondata, **self.kwargs) 134 | else: 135 | r = requests.post(url, **self.kwargs) 136 | response = r.json() 137 | return response 138 | 139 | def _api_put(self, controller, data=None): 140 | """ 141 | Internal method used to send HTTP PUT 142 | """ 143 | uri = controller 144 | url = self.baseurl + uri 145 | if data is not None: 146 | try: 147 | jsondata = json.dumps(data) 148 | except: 149 | jsondata = json.dumps({}) 150 | r = requests.put(url, data=jsondata, **self.kwargs) 151 | else: 152 | r = requests.put(url, **self.kwargs) 153 | response = r.json() 154 | return response 155 | 156 | def _api_patch(self, controller, data=None): 157 | """ 158 | Internal method used to send HTTP PATCH 159 | """ 160 | uri = controller 161 | url = self.baseurl + uri 162 | if data is not None: 163 | try: 164 | jsondata = json.dumps(data) 165 | except: 166 | jsondata = json.dumps({}) 167 | r = requests.patch(url, data=jsondata, **self.kwargs) 168 | else: 169 | r = requests.patch(url, **self.kwargs) 170 | response = r.json() 171 | return response 172 | 173 | def _api_delete(self, controller): 174 | """ 175 | Internal method used to send HTTP DELETE 176 | """ 177 | uri = controller 178 | url = self.baseurl + uri 179 | r = requests.delete(url, **self.kwargs) 180 | response = r.json() 181 | return response 182 | 183 | def auth(self, authmethod='Digest', login=False, logout=False): 184 | """ 185 | Authenticate to the SonicWALL Appliance 186 | 187 | Keyword arguments: 188 | ------------------ 189 | authmethod : str 190 | Either 'Basic' or 'Digest' (Defaults to Digest) 191 | HTTP Basic auth requires this to be enabled on your Appliance which is not enabled by default 192 | login : bool 193 | Set this to True if logging in 194 | logout : bool 195 | Set this to True if logging out 196 | """ 197 | controller = 'auth' 198 | url = self.baseurl + controller 199 | response = self.response 200 | if authmethod == 'Digest' and login: 201 | self.kwargs['auth'] = HTTPDigestAuth(self.authinfo[0], self.authinfo[1]) 202 | r = requests.head(url, **self.kwargs) 203 | if r.status_code == 200: 204 | response['status']['success'] = True 205 | response['status']['info'][0]['level'] = 'info' 206 | response['status']['info'][0]['code'] = 'E_OK' 207 | response['status']['info'][0]['message'] = 'Success.' 208 | elif authmethod == 'Basic' and login: 209 | r = requests.post(url, **self.kwargs) 210 | response = r.json() 211 | elif logout: 212 | r = requests.delete(url, **self.kwargs) 213 | response = r.json() 214 | return response 215 | 216 | def Preempt(self): 217 | """ 218 | ** Gen7 Only 219 | Preempt the other user, set self to config mode. 220 | """ 221 | controller = 'config-mode' 222 | response = self._api_post(controller) 223 | return response 224 | 225 | def NonConfig(self): 226 | """ 227 | ** Gen7 Only 228 | Release config mode, set self to non-config mode. 229 | """ 230 | controller = 'non-config-mode' 231 | response = self._api_post(controller) 232 | return response 233 | 234 | def Version(self): 235 | """ 236 | Outputs version information 237 | """ 238 | controller = 'version' 239 | response = self._api_get(controller) 240 | return response 241 | 242 | def getConfig(self): 243 | """ 244 | Returns any pending changes 245 | """ 246 | controller = 'config/current' 247 | response = self._api_get(controller) 248 | return response 249 | 250 | def getPendingChanges(self): 251 | """ 252 | Returns any pending changes 253 | """ 254 | controller = 'config/pending' 255 | response = self._api_get(controller) 256 | return response 257 | 258 | def commitPendingChanges(self): 259 | """ 260 | Commits all pending changes to the running and startup config 261 | """ 262 | controller = 'config/pending' 263 | response = self._api_post(controller) 264 | return response 265 | 266 | def AddressObjects(self, objectlist=[], method='get', type='ipv4', name=None, uuid=None): 267 | """ 268 | Interact with Address Objects 269 | 270 | Keyword arguments: 271 | method -- HTTP method to use ('get', 'post', 'put', 'delete') 272 | type -- 'ipv4', 'ipv6', 'mac', or 'fqdn' 273 | objectlist -- list of address objects you are creating/deleting/modifying. 274 | name -- Optional string containing the name of the object you wish to interact with. 275 | uuid -- Optional string containing the uuid of the object you wish to interact with. 276 | """ 277 | response = self.response 278 | validmethods = ['get', 'post', 'put', 'delete'] 279 | if method not in validmethods: 280 | response['status']['success'] = False 281 | response['status']['info'][0]['level'] = 'error' 282 | response['status']['info'][0]['code'] = 'E_INVALID' 283 | response['status']['info'][0]['message'] = 'Invalid Method.' 284 | return response 285 | controller = 'address-objects/{}/'.format(type) 286 | if name is not None: 287 | controller = '{}name/{}'.format(controller, name) 288 | if uuid is not None: 289 | controller = '{}name/{}'.format(controller, uuid) 290 | data = { 291 | 'address_objects': objectlist 292 | } 293 | if method == 'post': 294 | response = self._api_post(controller, data) 295 | elif method == 'put': 296 | response = self._api_put(controller, data) 297 | elif method == 'delete': 298 | response = self._api_delete(controller) 299 | else: 300 | response = self._api_get(controller) 301 | return response 302 | 303 | def AddressGroups(self, objectlist=[], method='get', ipversion='ipv4', name=None, uuid=None): 304 | """ 305 | Interact with Address Groups 306 | 307 | Keyword arguments: 308 | method -- HTTP method to use ('get', 'post', 'put', 'delete') 309 | ipversion -- 'ipv4' or 'ipv6' 310 | objectlist -- list of address groups you are creating/deleting/modifying. 311 | name -- Optional string containing the name of the group you wish to interact with. 312 | uuid -- Optional string containing the uuid of the group you wish to interact with. 313 | """ 314 | response = self.response 315 | validmethods = ['get', 'post', 'put', 'delete'] 316 | if method not in validmethods: 317 | response['status']['success'] = False 318 | response['status']['info'][0]['level'] = 'error' 319 | response['status']['info'][0]['code'] = 'E_INVALID' 320 | response['status']['info'][0]['message'] = 'Invalid Method.' 321 | return response 322 | controller = 'address-groups/{}/'.format(ipversion) 323 | if name is not None: 324 | controller = '{}name/{}'.format(controller, name) 325 | if uuid is not None: 326 | controller = '{}name/{}'.format(controller, uuid) 327 | data = { 328 | 'address_groups': objectlist 329 | } 330 | if method == 'post': 331 | response = self._api_post(controller, data) 332 | elif method == 'put': 333 | response = self._api_put(controller, data) 334 | elif method == 'delete': 335 | response = self._api_delete(controller) 336 | else: 337 | response = self._api_get(controller) 338 | return response 339 | 340 | def ServiceObjects(self, objectlist=[], method='get', name=None, uuid=None): 341 | """ 342 | Interact with Service Objects 343 | 344 | Keyword arguments: 345 | method -- HTTP method to use ('get', 'post', 'put', 'delete') 346 | objectlist -- list of service objects you are creating/deleting/modifying. 347 | name -- Optional string containing the name of the object you wish to interact with. 348 | uuid -- Optional string containing the uuid of the object you wish to interact with. 349 | """ 350 | response = self.response 351 | validmethods = ['get', 'post', 'put', 'delete'] 352 | if method not in validmethods: 353 | response['status']['success'] = False 354 | response['status']['info'][0]['level'] = 'error' 355 | response['status']['info'][0]['code'] = 'E_INVALID' 356 | response['status']['info'][0]['message'] = 'Invalid Method.' 357 | return response 358 | controller = 'service-objects/' 359 | if name is not None: 360 | controller = '{}name/{}'.format(controller, name) 361 | if uuid is not None: 362 | controller = '{}name/{}'.format(controller, uuid) 363 | data = { 364 | 'service_objects': objectlist 365 | } 366 | if method == 'post': 367 | response = self._api_post(controller, data) 368 | elif method == 'put': 369 | response = self._api_put(controller, data) 370 | elif method == 'delete': 371 | response = self._api_delete(controller) 372 | else: 373 | response = self._api_get(controller) 374 | return response 375 | 376 | def ServiceGroups(self, objectlist=[], method='get', name=None, uuid=None): 377 | """ 378 | Interact with Service Groups 379 | 380 | Keyword arguments: 381 | method -- HTTP method to use ('get', 'post', 'put', 'delete') 382 | objectlist -- list of service groups you are creating/deleting/modifying. 383 | name -- Optional string containing the name of the group you wish to interact with. 384 | uuid -- Optional string containing the uuid of the group you wish to interact with. 385 | """ 386 | response = self.response 387 | validmethods = ['get', 'post', 'put', 'delete'] 388 | if method not in validmethods: 389 | response['status']['success'] = False 390 | response['status']['info'][0]['level'] = 'error' 391 | response['status']['info'][0]['code'] = 'E_INVALID' 392 | response['status']['info'][0]['message'] = 'Invalid Method.' 393 | return response 394 | controller = 'service-groups/' 395 | if name is not None: 396 | controller = '{}name/{}'.format(controller, name) 397 | if uuid is not None: 398 | controller = '{}name/{}'.format(controller, uuid) 399 | data = { 400 | 'service_groups': objectlist 401 | } 402 | if method == 'post': 403 | response = self._api_post(controller, data) 404 | elif method == 'put': 405 | response = self._api_put(controller, data) 406 | elif method == 'delete': 407 | response = self._api_delete(controller) 408 | else: 409 | response = self._api_get(controller) 410 | return response 411 | 412 | def Zones(self, objectlist=[], method='get', name=None, uuid=None): 413 | """ 414 | Interact with Zones 415 | 416 | Keyword arguments: 417 | method -- HTTP method to use ('get', 'post', 'put', 'delete') 418 | objectlist -- list of zones you are creating/deleting/modifying. 419 | name -- Optional string containing the name of the zone you wish to interact with. 420 | uuid -- Optional string containing the uuid of the zone you wish to interact with. 421 | """ 422 | response = self.response 423 | validmethods = ['get', 'post', 'put', 'delete'] 424 | if method not in validmethods: 425 | response['status']['success'] = False 426 | response['status']['info'][0]['level'] = 'error' 427 | response['status']['info'][0]['code'] = 'E_INVALID' 428 | response['status']['info'][0]['message'] = 'Invalid Method.' 429 | return response 430 | controller = 'zones/' 431 | 432 | if name is not None: 433 | controller = '{}name/{}'.format(controller, name) 434 | if uuid is not None: 435 | controller = '{}uuid/{}'.format(controller, uuid) 436 | 437 | if name is not None or uuid is not None: 438 | data = { 439 | 'zone': objectlist 440 | } 441 | else: 442 | data = { 443 | 'zones': objectlist 444 | } 445 | 446 | if method == 'post': 447 | response = self._api_post(controller, data) 448 | elif method == 'put': 449 | response = self._api_put(controller, data) 450 | elif method == 'delete': 451 | response = self._api_delete(controller) 452 | else: 453 | response = self._api_get(controller) 454 | return response 455 | 456 | def NatPolicies(self, objectlist=[], method='get', ipversion='ipv4', uuid=None): 457 | """ 458 | Interact with NAT Policies 459 | 460 | Keyword arguments: 461 | method -- HTTP method to use ('get', 'post', 'put', 'delete') 462 | objectlist -- list of NAT Policies you are creating/deleting/modifying. 463 | uuid -- Optional string containing the uuid of the NAT Policy you wish to interact with. 464 | """ 465 | response = self.response 466 | validmethods = ['get', 'post', 'put', 'delete'] 467 | if method not in validmethods: 468 | response['status']['success'] = False 469 | response['status']['info'][0]['level'] = 'error' 470 | response['status']['info'][0]['code'] = 'E_INVALID' 471 | response['status']['info'][0]['message'] = 'Invalid Method.' 472 | return response 473 | controller = 'nat-policies/' 474 | 475 | if ipversion == 'ipv6': 476 | controller = '{}ipv6/'.format(controller) 477 | else: 478 | controller = '{}ipv4/'.format(controller) 479 | 480 | if uuid is not None: 481 | controller = '{}uuid/{}'.format(controller, uuid) 482 | 483 | data = { 484 | 'nat_policies': objectlist 485 | } 486 | 487 | if method == 'post': 488 | response = self._api_post(controller, data) 489 | elif method == 'put': 490 | response = self._api_put(controller, data) 491 | elif method == 'delete': 492 | response = self._api_delete(controller) 493 | else: 494 | response = self._api_get(controller) 495 | return response 496 | 497 | def AccessRules(self, objectlist=[], method='get', ipversion='ipv4', uuid=None): 498 | """ 499 | Interact with Access Rules 500 | 501 | Keyword arguments: 502 | method -- HTTP method to use ('get', 'post', 'put', 'delete') 503 | objectlist -- list of Access Rules you are creating/deleting/modifying. 504 | uuid -- Optional string containing the uuid of the Access Rule you wish to interact with. 505 | """ 506 | try: 507 | myversion = list(self.Version()['firmware_version'].split()[-1])[0] 508 | except: 509 | myversion = '6' 510 | 511 | response = self.response 512 | validmethods = ['get', 'post', 'put', 'delete'] 513 | if method not in validmethods: 514 | response['status']['success'] = False 515 | response['status']['info'][0]['level'] = 'error' 516 | response['status']['info'][0]['code'] = 'E_INVALID' 517 | response['status']['info'][0]['message'] = 'Invalid Method.' 518 | return response 519 | if myversion == '6': 520 | controller = 'access-rules/' 521 | elif myversion == '7': 522 | controller = 'security-policies/' 523 | 524 | if ipversion == 'ipv6': 525 | controller = '{}ipv6/'.format(controller) 526 | else: 527 | controller = '{}ipv4/'.format(controller) 528 | 529 | if uuid is not None: 530 | controller = '{}uuid/{}'.format(controller, uuid) 531 | 532 | if myversion == '6': 533 | data = { 534 | 'access_rules': objectlist 535 | } 536 | elif myversion == '7': 537 | data = { 538 | 'security_policies': objectlist 539 | } 540 | 541 | if method == 'post': 542 | response = self._api_post(controller, data) 543 | elif method == 'put': 544 | response = self._api_put(controller, data) 545 | elif method == 'delete': 546 | response = self._api_delete(controller) 547 | else: 548 | response = self._api_get(controller) 549 | return response 550 | 551 | def RoutePolicies(self, objectlist=[], method='get', ipversion='ipv4', uuid=None): 552 | """ 553 | Interact with Route Policies 554 | 555 | Keyword arguments: 556 | objectlist -- list of Route Policies you are creating/deleting/modifying. 557 | method -- HTTP method to use ('get', 'post', 'put', 'delete') Defaults to 'get' 558 | ipversion -- ipv4 or ipv6 - Defaults to 'ipv4' 559 | uuid -- Optional string containing the uuid of the Route Policy you wish to interact with. 560 | """ 561 | response = self.response 562 | validmethods = ['get', 'post', 'put', 'delete'] 563 | if method not in validmethods: 564 | response['status']['success'] = False 565 | response['status']['info'][0]['level'] = 'error' 566 | response['status']['info'][0]['code'] = 'E_INVALID' 567 | response['status']['info'][0]['message'] = 'Invalid Method.' 568 | return response 569 | controller = 'route-policies/' 570 | 571 | if ipversion == 'ipv6': 572 | controller = '{}ipv6/'.format(controller) 573 | else: 574 | controller = '{}ipv4/'.format(controller) 575 | 576 | if uuid is not None: 577 | controller = '{}uuid/{}'.format(controller, uuid) 578 | 579 | data = { 580 | 'route_policies': objectlist 581 | } 582 | 583 | if method == 'post': 584 | response = self._api_post(controller, data) 585 | elif method == 'put': 586 | response = self._api_put(controller, data) 587 | elif method == 'delete': 588 | response = self._api_delete(controller) 589 | else: 590 | response = self._api_get(controller) 591 | return response 592 | 593 | def Restart(self, at=None, minutes=None, hours=None, days=None): 594 | """ 595 | Reboot the SonicWALL Appliance. 596 | (Defaults to restart the appliance immediately) 597 | 598 | Keyword arguments: 599 | at -- Optional str: Timestamp in the form: 'YYYY:MM:DD:HH:MM:SS' 600 | minutes -- Optional int: Number of minutes in the future 601 | hours -- Optional int: Number of hours in the future 602 | days -- Optional int: Number of days in the future 603 | """ 604 | controller = 'restart/' 605 | if at is not None and minutes is None and hours is None and days is None: 606 | pass 607 | elif minutes is not None and at is None and hours is None and days is None: 608 | controller = '{}in/{}/minutes'.format(controller, minutes) 609 | elif hours is not None and at is None and minutes is None and days is None: 610 | controller = '{}in/{}/hours'.format(controller, hours) 611 | elif days is not None and at is None and minutes is None and hours is None: 612 | controller = '{}in/{}/days'.format(controller, days) 613 | 614 | response = self._api_post(controller) 615 | return response 616 | 617 | def VpnPolicy(self, method='get', ipversion='ipv4', vpntype='site-to-site', vpnname=None, data=None): 618 | """ 619 | Interact with VPN Policies. 620 | 621 | Keyword arguments: 622 | method -- HTTP method to use ('get', 'post', 'put', 'patch', 'delete') Defaults to 'get' 623 | ipversion -- ipv4 or ipv6 - Defaults to 'ipv4' 624 | vpntype -- Type of VPN ('site-to-site') Defaults to 'site-to-site' 625 | vpnname -- Name of VPN (optional) 626 | data -- Payload (optional) 627 | """ 628 | controller = f"vpn/policies/{ipversion}/{vpntype}" 629 | if vpnname: 630 | controller = f"{controller}/name/{vpnname}" 631 | if method == 'delete': 632 | response = self._api_delete(controller) 633 | 634 | if method == 'post': 635 | response = self._api_post(controller, data) 636 | elif method == 'put': 637 | response = self._api_put(controller, data) 638 | elif method == 'patch': 639 | response = self._api_patch(controller, data) 640 | else: 641 | response = self._api_get(controller) 642 | return response 643 | --------------------------------------------------------------------------------