zscaler-sdk-python 1.0.0__py2.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.
Files changed (75) hide show
  1. zscaler/__init__.py +34 -0
  2. zscaler/cache/__init__.py +0 -0
  3. zscaler/cache/cache.py +105 -0
  4. zscaler/cache/no_op_cache.py +68 -0
  5. zscaler/cache/zscaler_cache.py +161 -0
  6. zscaler/constants.py +26 -0
  7. zscaler/errors/__init__.py +0 -0
  8. zscaler/errors/error.py +10 -0
  9. zscaler/errors/http_error.py +20 -0
  10. zscaler/errors/zscaler_api_error.py +24 -0
  11. zscaler/exceptions/__init__.py +1 -0
  12. zscaler/exceptions/exceptions.py +101 -0
  13. zscaler/logger.py +57 -0
  14. zscaler/ratelimiter/__init__.py +0 -0
  15. zscaler/ratelimiter/ratelimiter.py +39 -0
  16. zscaler/user_agent.py +23 -0
  17. zscaler/utils.py +577 -0
  18. zscaler/zia/__init__.py +657 -0
  19. zscaler/zia/activate.py +52 -0
  20. zscaler/zia/admin_and_role_management.py +344 -0
  21. zscaler/zia/apptotal.py +71 -0
  22. zscaler/zia/audit_logs.py +95 -0
  23. zscaler/zia/authentication_settings.py +98 -0
  24. zscaler/zia/client.py +88 -0
  25. zscaler/zia/cloud_apps.py +406 -0
  26. zscaler/zia/device_management.py +90 -0
  27. zscaler/zia/dlp.py +784 -0
  28. zscaler/zia/errors.py +37 -0
  29. zscaler/zia/firewall.py +1104 -0
  30. zscaler/zia/forwarding_control.py +271 -0
  31. zscaler/zia/isolation_profile.py +83 -0
  32. zscaler/zia/labels.py +180 -0
  33. zscaler/zia/locations.py +661 -0
  34. zscaler/zia/sandbox.py +180 -0
  35. zscaler/zia/security.py +236 -0
  36. zscaler/zia/ssl_inspection.py +175 -0
  37. zscaler/zia/traffic.py +853 -0
  38. zscaler/zia/url_categories.py +442 -0
  39. zscaler/zia/url_filtering.py +310 -0
  40. zscaler/zia/users.py +386 -0
  41. zscaler/zia/web_dlp.py +295 -0
  42. zscaler/zia/workload_groups.py +58 -0
  43. zscaler/zia/zpa_gateway.py +187 -0
  44. zscaler/zpa/__init__.py +683 -0
  45. zscaler/zpa/app_segments.py +331 -0
  46. zscaler/zpa/app_segments_inspection.py +311 -0
  47. zscaler/zpa/app_segments_pra.py +310 -0
  48. zscaler/zpa/certificates.py +234 -0
  49. zscaler/zpa/client.py +113 -0
  50. zscaler/zpa/cloud_connector_groups.py +75 -0
  51. zscaler/zpa/connectors.py +518 -0
  52. zscaler/zpa/emergency_access.py +178 -0
  53. zscaler/zpa/errors.py +37 -0
  54. zscaler/zpa/idp.py +83 -0
  55. zscaler/zpa/inspection.py +1012 -0
  56. zscaler/zpa/isolation_profile.py +85 -0
  57. zscaler/zpa/lss.py +568 -0
  58. zscaler/zpa/machine_groups.py +79 -0
  59. zscaler/zpa/policies.py +848 -0
  60. zscaler/zpa/posture_profiles.py +122 -0
  61. zscaler/zpa/privileged_remote_access.py +862 -0
  62. zscaler/zpa/provisioning.py +271 -0
  63. zscaler/zpa/saml_attributes.py +100 -0
  64. zscaler/zpa/scim_attributes.py +117 -0
  65. zscaler/zpa/scim_groups.py +146 -0
  66. zscaler/zpa/segment_groups.py +191 -0
  67. zscaler/zpa/server_groups.py +217 -0
  68. zscaler/zpa/servers.py +202 -0
  69. zscaler/zpa/service_edges.py +404 -0
  70. zscaler/zpa/trusted_networks.py +127 -0
  71. zscaler_sdk_python-1.0.0.dist-info/LICENSE.md +21 -0
  72. zscaler_sdk_python-1.0.0.dist-info/METADATA +59 -0
  73. zscaler_sdk_python-1.0.0.dist-info/RECORD +75 -0
  74. zscaler_sdk_python-1.0.0.dist-info/WHEEL +6 -0
  75. zscaler_sdk_python-1.0.0.dist-info/top_level.txt +1 -0
zscaler/zia/traffic.py ADDED
@@ -0,0 +1,853 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ # Copyright (c) 2023, Zscaler Inc.
4
+ #
5
+ # Permission to use, copy, modify, and/or distribute this software for any
6
+ # purpose with or without fee is hereby granted, provided that the above
7
+ # copyright notice and this permission notice appear in all copies.
8
+ #
9
+ # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
+ # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
+ # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
+ # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
+ # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
+ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
+ # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16
+
17
+
18
+ from box import Box, BoxList
19
+ from requests import Response
20
+
21
+ from zscaler.utils import Iterator, convert_keys, snake_to_camel
22
+ from zscaler.zia import ZIAClient
23
+
24
+
25
+ class TrafficForwardingAPI:
26
+ def __init__(self, client: ZIAClient):
27
+ self.rest = client
28
+
29
+ def list_gre_tunnels(self, **kwargs) -> BoxList:
30
+ """
31
+ Returns the list of all configured GRE tunnels.
32
+
33
+ Keyword Args:
34
+ **max_items (int, optional):
35
+ The maximum number of items to request before stopping iteration.
36
+ **max_pages (int, optional):
37
+ The maximum number of pages to request before stopping iteration.
38
+ **page_size (int, optional):
39
+ Specifies the page size. The default size is 100, but the maximum size is 1000.
40
+
41
+ Returns:
42
+ :obj:`BoxList`: A list of GRE tunnels configured in ZIA.
43
+
44
+ Examples:
45
+ List GRE tunnels with default settings:
46
+
47
+ >>> for tunnel in zia.traffic.list_gre_tunnels():
48
+ ... print(tunnel)
49
+
50
+ List GRE tunnels, limiting to a maximum of 10 items:
51
+
52
+ >>> for tunnel in zia.traffic.list_gre_tunnels(max_items=10):
53
+ ... print(tunnel)
54
+
55
+ List GRE tunnels, returning 200 items per page for a maximum of 2 pages:
56
+
57
+ >>> for tunnel in zia.traffic.list_gre_tunnels(page_size=200, max_pages=2):
58
+ ... print(tunnel)
59
+
60
+ """
61
+ return BoxList(Iterator(self.rest, "greTunnels", **kwargs))
62
+
63
+ def get_gre_tunnel(self, tunnel_id: str) -> Box:
64
+ """
65
+ Returns information for the specified GRE tunnel.
66
+
67
+ Args:
68
+ tunnel_id (str):
69
+ The unique identifier for the GRE tunnel.
70
+
71
+ Returns:
72
+ :obj:`Box`: The GRE tunnel resource record.
73
+
74
+ Examples:
75
+ >>> gre_tunnel = zia.traffic.get_gre_tunnel('967134')
76
+
77
+ """
78
+ return self.rest.get(f"greTunnels/{tunnel_id}")
79
+
80
+ def list_gre_ranges(self, **kwargs) -> BoxList:
81
+ """
82
+ Returns a list of available GRE tunnel ranges.
83
+
84
+ Keyword Args:
85
+ **internal_ip_range (str, optional):
86
+ Internal IP range information.
87
+ **static_ip (str, optional):
88
+ Static IP information.
89
+ **limit (int, optional):
90
+ The maximum number of GRE tunnel IP ranges that can be added. Defaults to `10`.
91
+
92
+ Returns:
93
+ :obj:`BoxList`: A list of available GRE tunnel ranges.
94
+
95
+ Examples:
96
+ >>> gre_tunnel_ranges = zia.traffic.list_gre_ranges()
97
+
98
+ """
99
+ payload = {snake_to_camel(key): value for key, value in kwargs.items()}
100
+
101
+ response = self.rest.get("greTunnels/availableInternalIpRanges", params=payload)
102
+ if isinstance(response, Response):
103
+ return None
104
+ return response
105
+
106
+ def list_vips_recommended(self, source_ip: str, **kwargs) -> BoxList:
107
+ """
108
+ Returns a list of recommended virtual IP addresses (VIPs) based on parameters.
109
+
110
+ Args:
111
+ source_ip (str):
112
+ The source IP address.
113
+ **kwargs:
114
+ Optional keywords args.
115
+
116
+ Keyword Args:
117
+ routable_ip (bool):
118
+ The routable IP address. Default: True.
119
+ within_country_only (bool):
120
+ Search within country only. Default: False.
121
+ include_private_service_edge (bool):
122
+ Include ZIA Private Service Edge VIPs. Default: True.
123
+ include_current_vips (bool):
124
+ Include currently assigned VIPs. Default: True.
125
+ latitude (str):
126
+ Latitude coordinate of GRE tunnel source.
127
+ longitude (str):
128
+ Longitude coordinate of GRE tunnel source.
129
+ geo_override (bool):
130
+ Override the geographic coordinates. Default: False.
131
+
132
+ Returns:
133
+ :obj:`BoxList`: List of VIP resource records.
134
+
135
+ Examples:
136
+ Return recommended VIPs for a given source IP:
137
+
138
+ >>> for vip in zia.traffic.list_vips_recommende(source_ip='203.0.113.30'):
139
+ ... pprint(vip)
140
+
141
+ """
142
+ payload = {"sourceIp": source_ip}
143
+
144
+ # Add optional parameters to payload
145
+ for key, value in kwargs.items():
146
+ payload[snake_to_camel(key)] = value
147
+
148
+ response = self.rest.get("vips/recommendedList", params=payload, **kwargs)
149
+ if isinstance(response, Response):
150
+ return None
151
+ return response
152
+
153
+ def get_closest_diverse_vip_ids(self, ip_address: str) -> tuple:
154
+ """
155
+ Returns the closest diverse Zscaler destination VIPs for a given IP address.
156
+
157
+ Args:
158
+ ip_address (str):
159
+ The IP address used for locating the closest diverse VIPs.
160
+
161
+ Returns:
162
+ :obj:`tuple`: Tuple containing the preferred and secondary VIP IDs.
163
+
164
+ Examples:
165
+ >>> closest_vips = zia.traffic.get_closest_diverse_vip_ids('203.0.113.20')
166
+
167
+ """
168
+ vips_list = self.list_vips_recommended(source_ip=ip_address)
169
+ preferred_vip = vips_list[0] # First entry is closest vip
170
+
171
+ # Generator to find the next closest vip not in the same city as our preferred
172
+ secondary_vip = next(
173
+ (vip for vip in vips_list if vip.city != preferred_vip.city)
174
+ )
175
+ recommended_vips = (preferred_vip.id, secondary_vip.id)
176
+
177
+ return recommended_vips
178
+
179
+ def list_vip_group_by_dc(self, source_ip: str, **kwargs) -> BoxList:
180
+ """
181
+ Returns a list of recommended GRE tunnel (VIPs) grouped by data center.
182
+
183
+ Args:
184
+ source_ip (str):
185
+ The source IP address.
186
+ **kwargs:
187
+ Optional keywords args.
188
+
189
+ Keyword Args:
190
+ routable_ip (bool):
191
+ The routable IP address. Default: True.
192
+ within_country_only (bool):
193
+ Search within country only. Default: False.
194
+ include_private_service_edge (bool):
195
+ Include ZIA Private Service Edge VIPs. Default: True.
196
+ include_current_vips (bool):
197
+ Include currently assigned VIPs. Default: True.
198
+ latitude (str):
199
+ Latitude coordinate of GRE tunnel source.
200
+ longitude (str):
201
+ Longitude coordinate of GRE tunnel source.
202
+ geo_override (bool):
203
+ Override the geographic coordinates. Default: False.
204
+ Returns:
205
+ :obj:`BoxList`: List of VIP resource records.
206
+
207
+ Examples:
208
+ Return recommended VIPs for a given source IP:
209
+
210
+ >>> for vip in zia.vips.list_vip_group_by_dc(source_ip='203.0.113.30'):
211
+ ... pprint(vip)
212
+
213
+ """
214
+ params = {"sourceIp": source_ip}
215
+
216
+ for key, value in kwargs.items():
217
+ params[snake_to_camel(key)] = value
218
+ response = self.rest.get("/vips/groupByDatacenter", params=params)
219
+ if response is not None:
220
+ return response
221
+ else:
222
+ print(
223
+ "Failed to fetch VIP groups by data center. No response or error received."
224
+ )
225
+ return BoxList([])
226
+
227
+ def list_vips(self, **kwargs) -> BoxList:
228
+ """
229
+ Returns a list of virtual IP addresses (VIPs) available in the Zscaler cloud.
230
+
231
+ Keyword Args:
232
+ **dc (str, optional):
233
+ Filter based on data center.
234
+ **include (str, optional):
235
+ Include all, private, or public VIPs in the list. Available choices are `all`, `private`, `public`.
236
+ Defaults to `public`.
237
+ **max_items (int, optional):
238
+ The maximum number of items to request before stopping iteration.
239
+ **max_pages (int, optional):
240
+ The maximum number of pages to request before stopping iteration.
241
+ **page_size (int, optional):
242
+ Specifies the page size. The default size is 100, but the maximum size is 1000.
243
+ **region (str, optional):
244
+ Filter based on region.
245
+
246
+ Returns:
247
+ :obj:`BoxList`: List of VIP resource records.
248
+
249
+ Examples:
250
+ List VIPs using default settings:
251
+
252
+ >>> for vip in zia.vips.list_vips():
253
+ ... pprint(vip)
254
+
255
+ List VIPs, limiting to a maximum of 10 items:
256
+
257
+ >>> for vip in zia.vips.list_vips(max_items=10):
258
+ ... print(vip)
259
+
260
+ List VIPs, returning 200 items per page for a maximum of 2 pages:
261
+
262
+ >>> for vip in zia.traffic.list_vips(page_size=200, max_pages=2):
263
+ ... print(vip)
264
+
265
+ """
266
+ return BoxList(Iterator(self.rest, "vips", **kwargs))
267
+
268
+ def add_gre_tunnel(
269
+ self,
270
+ source_ip: str,
271
+ primary_dest_vip_id: str = None,
272
+ secondary_dest_vip_id: str = None,
273
+ **kwargs,
274
+ ) -> Box:
275
+ """
276
+ Add a new GRE tunnel.
277
+
278
+ Note: If the `primary_dest_vip_id` and `secondary_dest_vip_id` aren't specified then the closest recommended
279
+ VIPs will be automatically chosen.
280
+
281
+ Args:
282
+ source_ip (str):
283
+ The source IP address of the GRE tunnel. This is typically a static IP address in the organisation
284
+ or SD-WAN.
285
+ primary_dest_vip_id (str):
286
+ The unique identifier for the primary destination virtual IP address (VIP) of the GRE tunnel.
287
+ Defaults to the closest recommended VIP.
288
+ secondary_dest_vip_id (str):
289
+ The unique identifier for the secondary destination virtual IP address (VIP) of the GRE tunnel.
290
+ Defaults to the closest recommended VIP that isn't in the same city as the primary VIP.
291
+
292
+ Keyword Args:
293
+ **comment (str):
294
+ Additional information about this GRE tunnel
295
+ **ip_unnumbered (bool):
296
+ This is required to support the automated SD-WAN provisioning of GRE tunnels, when set to true
297
+ gre_tun_ip and gre_tun_id are set to null
298
+ **internal_ip_range (str):
299
+ The start of the internal IP address in /29 CIDR range.
300
+ **within_country (bool):
301
+ Restrict the data center virtual IP addresses (VIPs) only to those within the same country as the
302
+ source IP address.
303
+
304
+ Returns:
305
+ :obj:`Box`: The resource record for the newly created GRE tunnel.
306
+
307
+ Examples:
308
+ Add a GRE tunnel with closest recommended VIPs:
309
+
310
+ >>> zia.traffic.add_gre_tunnel('203.0.113.10')
311
+
312
+ Add a GRE tunnel with explicit VIPs:
313
+
314
+ >>> zia.traffic.add_gre_tunnel('203.0.113.11',
315
+ ... primary_dest_vip_id='88088',
316
+ ... secondary_dest_vip_id='54590',
317
+ ... comment='GRE Tunnel for Manufacturing Plant')
318
+
319
+ """
320
+
321
+ # If primary/secondary VIPs not provided, add the closest diverse VIPs
322
+ if primary_dest_vip_id is None and secondary_dest_vip_id is None:
323
+ recommended_vips = self.get_closest_diverse_vip_ids(source_ip)
324
+ primary_dest_vip_id = recommended_vips[0]
325
+ secondary_dest_vip_id = recommended_vips[1]
326
+
327
+ payload = {
328
+ "sourceIp": source_ip,
329
+ "primaryDestVip": {"id": primary_dest_vip_id},
330
+ "secondaryDestVip": {"id": secondary_dest_vip_id},
331
+ }
332
+
333
+ # Add optional parameters to payload
334
+ for key, value in kwargs.items():
335
+ payload[snake_to_camel(key)] = value
336
+
337
+ response = self.rest.post("greTunnels", json=payload)
338
+ if isinstance(response, Response):
339
+ # this is only true when the creation failed (status code is not 2xx)
340
+ status_code = response.status_code
341
+ # Handle error response
342
+ raise Exception(
343
+ f"API call failed with status {status_code}: {response.json()}"
344
+ )
345
+ return response
346
+
347
+ def update_gre_tunnel(
348
+ self,
349
+ tunnel_id: str,
350
+ source_ip: str = None,
351
+ primary_dest_vip_id: str = None,
352
+ secondary_dest_vip_id: str = None,
353
+ **kwargs,
354
+ ) -> Box:
355
+ """
356
+ Update an existing GRE tunnel.
357
+ """
358
+
359
+ if tunnel_id is None:
360
+ raise ValueError(
361
+ "tunnel_id is a required parameter for updating a GRE tunnel."
362
+ )
363
+
364
+ # Determine VIPs based on source_ip if not provided
365
+ if primary_dest_vip_id is None or secondary_dest_vip_id is None:
366
+ recommended_vips = self.get_closest_diverse_vip_ids(source_ip)
367
+ primary_dest_vip_id = primary_dest_vip_id or recommended_vips[0]
368
+ secondary_dest_vip_id = secondary_dest_vip_id or recommended_vips[1]
369
+
370
+ payload = {
371
+ "sourceIp": source_ip,
372
+ "primaryDestVip": {"id": primary_dest_vip_id},
373
+ "secondaryDestVip": {"id": secondary_dest_vip_id},
374
+ }
375
+
376
+ # Include additional optional parameters
377
+ for key, value in kwargs.items():
378
+ payload[snake_to_camel(key)] = value
379
+
380
+ response = self.rest.put(f"greTunnels/{tunnel_id}", json=payload)
381
+ if isinstance(response, Response) and not response.ok:
382
+ # Handle error response
383
+ raise Exception(
384
+ f"API call failed with status {response.status_code}: {response.json()}"
385
+ )
386
+ # Return the updated object
387
+ return self.get_gre_tunnel(tunnel_id)
388
+
389
+ def delete_gre_tunnel(self, tunnel_id: str) -> int:
390
+ """
391
+ Delete the specified static IP.
392
+
393
+ Args:
394
+ static_ip_id (str):
395
+ The unique identifier for the static IP.
396
+
397
+ Returns:
398
+ :obj:`int`: The response code for the operation.
399
+
400
+ Examples:
401
+ >>> zia.traffic.delete_gre_tunnel('972494')
402
+
403
+ """
404
+
405
+ return self.rest.delete(f"greTunnels/{tunnel_id}").status_code
406
+
407
+ def list_static_ips(self, **kwargs) -> BoxList:
408
+ """
409
+ Returns the list of all configured static IPs.
410
+
411
+ Keyword Args:
412
+ **available_for_gre_tunnel (bool, optional):
413
+ Only return the static IP addresses that are not yet associated with a GRE tunnel if True.
414
+ Defaults to False.
415
+ **ip_address (str, optional):
416
+ Filter based on IP address.
417
+ **max_items (int, optional):
418
+ The maximum number of items to request before stopping iteration.
419
+ **max_pages (int, optional):
420
+ The maximum number of pages to request before stopping iteration.
421
+ **page_size (int, optional):
422
+ Specifies the page size. The default size is 100, but the maximum size is 1000.
423
+
424
+ Returns:
425
+ :obj:`BoxList`: A list of the configured static IPs
426
+
427
+ Examples:
428
+ List static IPs using default settings:
429
+
430
+ >>> for ip_address in zia.traffic.list_static_ips():
431
+ ... print(ip_address)
432
+
433
+ List static IPs, limiting to a maximum of 10 items:
434
+
435
+ >>> for ip_address in zia.traffic.list_static_ips(max_items=10):
436
+ ... print(ip_address)
437
+
438
+ List static IPs, returning 200 items per page for a maximum of 2 pages:
439
+
440
+ >>> for ip_address in zia.traffic.list_static_ips(page_size=200, max_pages=2):
441
+ ... print(ip_address)
442
+
443
+ """
444
+ valid_params = ["availableForGreTunnel", "ipAddress"]
445
+ query_params = {
446
+ k: v for k, v in kwargs.items() if k in valid_params and v is not None
447
+ }
448
+
449
+ response = self.rest.get("/staticIP", params=query_params)
450
+ if isinstance(response, Response):
451
+ return None
452
+ return response
453
+
454
+ def get_static_ip(self, static_ip_id: str) -> Box:
455
+ """
456
+ Returns information for the specified static IP.
457
+
458
+ Args:
459
+ static_ip_id (str):
460
+ The unique identifier for the static IP.
461
+
462
+ Returns:
463
+ :obj:`dict`: The resource record for the static IP
464
+
465
+ Examples:
466
+ >>> static_ip = zia.traffic.get_static_ip('967134')
467
+
468
+ """
469
+ return self.rest.get(f"staticIP/{static_ip_id}")
470
+
471
+ def add_static_ip(self, ip_address: str, **kwargs) -> Box:
472
+ """
473
+ Adds a new static IP.
474
+
475
+ Args:
476
+ ip_address (str):
477
+ The static IP address
478
+
479
+ Keyword Args:
480
+ **comment (str):
481
+ Additional information about this static IP address.
482
+ **geo_override (bool):
483
+ If not set, geographic coordinates and city are automatically determined from the IP address.
484
+ Otherwise, the latitude and longitude coordinates must be provided.
485
+ **routable_ip (bool):
486
+ Indicates whether a non-RFC 1918 IP address is publicly routable. This attribute is ignored if there
487
+ is no ZIA Private Service Edge associated to the organization.
488
+ **latitude (float):
489
+ Required only if the geoOverride attribute is set. Latitude with 7 digit precision after
490
+ decimal point, ranges between -90 and 90 degrees.
491
+ **longitude (float):
492
+ Required only if the geoOverride attribute is set. Longitude with 7 digit precision after decimal
493
+ point, ranges between -180 and 180 degrees.
494
+
495
+ Returns:
496
+ :obj:`Box`: The resource record for the newly created static IP.
497
+
498
+ Examples:
499
+ Add a new static IP address:
500
+
501
+ >>> zia.traffic.add_static_ip(ip_address='203.0.113.10',
502
+ ... comment="Los Angeles Branch Office")
503
+
504
+ """
505
+
506
+ payload = {
507
+ "ipAddress": ip_address,
508
+ }
509
+
510
+ # Add optional parameters to payload
511
+ for key, value in kwargs.items():
512
+ payload[snake_to_camel(key)] = value
513
+
514
+ response = self.rest.post("staticIP", json=payload)
515
+ if isinstance(response, Response):
516
+ # this is only true when the creation failed (status code is not 2xx)
517
+ status_code = response.status_code
518
+ # Handle error response
519
+ raise Exception(
520
+ f"API call failed with status {status_code}: {response.json()}"
521
+ )
522
+ return response
523
+
524
+ def check_static_ip(self, ip_address: str) -> bool:
525
+ """
526
+ Validates if a static IP object is correct.
527
+
528
+ Args:
529
+ ip_address (str): The static IP address
530
+
531
+ Returns:
532
+ :obj:`bool`: True if the static IP provided is valid, False otherwise.
533
+
534
+ Examples:
535
+ >>> zia.traffic.check_static_ip(ip_address='203.0.113.11')
536
+ """
537
+ payload = {
538
+ "ipAddress": ip_address,
539
+ }
540
+ response = self.rest.post("staticIP/validate", json=payload)
541
+
542
+ # Check if the status code is 200 and the response body text is "SUCCESS"
543
+ if response.status_code == 200 and response.text.strip().upper() == "SUCCESS":
544
+ return True
545
+ else:
546
+ # Optionally, you could log response.text or response.status_code here for debugging
547
+ return False
548
+
549
+ def update_static_ip(self, static_ip_id: str, **kwargs) -> Box:
550
+ """
551
+ Updates information relating to the specified static IP.
552
+
553
+ Args:
554
+ static_ip_id (str):
555
+ The unique identifier for the static IP
556
+ **kwargs:
557
+ Optional keyword args.
558
+
559
+ Keyword Args:
560
+ **comment (str):
561
+ Additional information about this static IP address.
562
+ **geo_override (bool):
563
+ If not set, geographic coordinates and city are automatically determined from the IP address.
564
+ Otherwise, the latitude and longitude coordinates must be provided.
565
+ **routable_ip (bool):
566
+ Indicates whether a non-RFC 1918 IP address is publicly routable. This attribute is ignored if there
567
+ is no ZIA Private Service Edge associated to the organization.
568
+ **latitude (float):
569
+ Required only if the geoOverride attribute is set. Latitude with 7 digit precision after
570
+ decimal point, ranges between -90 and 90 degrees.
571
+ **longitude (float):
572
+ Required only if the geoOverride attribute is set. Longitude with 7 digit precision after decimal
573
+ point, ranges between -180 and 180 degrees.
574
+
575
+ Returns:
576
+ :obj:`Box`: The updated static IP resource record.
577
+
578
+ Examples:
579
+ >>> zia.traffic.update_static_ip('972494', comment='NY Branch Office')
580
+
581
+ """
582
+
583
+ payload = convert_keys(self.get_static_ip(static_ip_id))
584
+
585
+ # Add optional parameters to payload
586
+ for key, value in kwargs.items():
587
+ payload[snake_to_camel(key)] = value
588
+
589
+ response = self.rest.put(f"staticIP/{static_ip_id}", json=payload)
590
+ if isinstance(response, Response) and not response.ok:
591
+ # Handle error response
592
+ raise Exception(
593
+ f"API call failed with status {response.status_code}: {response.json()}"
594
+ )
595
+
596
+ # Return the updated object
597
+ return self.get_static_ip(static_ip_id)
598
+
599
+ def delete_static_ip(self, static_ip_id: str) -> int:
600
+ """
601
+ Delete the specified static IP.
602
+
603
+ Args:
604
+ static_ip_id (str):
605
+ The unique identifier for the static IP.
606
+
607
+ Returns:
608
+ :obj:`int`: The response code for the operation.
609
+
610
+ Examples:
611
+ >>> zia.traffic.delete_static_ip('972494')
612
+
613
+ """
614
+
615
+ return self.rest.delete(f"staticIP/{static_ip_id}").status_code
616
+
617
+ def list_vpn_credentials(self, **kwargs) -> BoxList:
618
+ """
619
+ Returns the list of all configured VPN credentials with optional filtering.
620
+
621
+ Args:
622
+ **kwargs:
623
+ Optional keyword search filters.
624
+
625
+ Keyword Args:
626
+ search (str, optional):
627
+ The search string used to match against a VPN credential's attributes.
628
+ type (str, optional):
629
+ Only gets VPN credentials for the specified type (CN, IP, UFQDN, XAUTH).
630
+ include_only_without_location (bool, optional):
631
+ Include VPN credential only if not associated to any location.
632
+ location_id (int, optional):
633
+ Gets the VPN credentials for the specified location ID.
634
+ managedBy (int, optional):
635
+ Gets the VPN credentials managed by the given partner.
636
+ max_items (int, optional):
637
+ The maximum number of items to request before stopping iteration.
638
+ max_pages (int, optional):
639
+ The maximum number of pages to request before stopping iteration.
640
+ page_size (int, optional):
641
+ Specifies the page size. The default size is 100, but the maximum size is 1000.
642
+
643
+ Returns:
644
+ :obj:`BoxList`: List containing the VPN credential resource records.
645
+
646
+ Examples:
647
+ List VPN credentials using default settings:
648
+
649
+ >>> for credential in zia.traffic.list_vpn_credentials:
650
+ ... pprint(credential)
651
+
652
+ List VPN credentials, limiting to a maximum of 10 items:
653
+
654
+ >>> for credential in zia.traffic.list_vpn_credentials(max_items=10):
655
+ ... print(credential)
656
+
657
+ List VPN credentials, returning 200 items per page for a maximum of 2 pages:
658
+
659
+ >>> for credential in zia.traffic.list_vpn_credentials(page_size=200, max_pages=2):
660
+ ... print(credential)
661
+ """
662
+ return BoxList(Iterator(self.rest, "vpnCredentials", **kwargs))
663
+
664
+ def add_vpn_credential(
665
+ self, authentication_type: str, pre_shared_key: str = None, **kwargs
666
+ ) -> Box:
667
+ """
668
+ Add new VPN credentials.
669
+
670
+ If a pre_shared_key is not provided, one will be randomly generated.
671
+
672
+ Args:
673
+ authentication_type (str):
674
+ VPN authentication type (i.e., how the VPN credential is sent to the server). It is not modifiable
675
+ after VpnCredential is created.
676
+
677
+ Only ``IP`` and ``UFQDN`` supported via API.
678
+ pre_shared_key (str, optional):
679
+ This field is required for UFQDN and IP auth type. If not provided a random one will be generated.
680
+
681
+ Keyword Args:
682
+ ip_address (str):
683
+ The static IP address associated with these VPN credentials.
684
+ fqdn (str):
685
+ Fully Qualified Domain Name. Applicable only to UFQDN auth type. This must be provided in the format
686
+ `userid@fqdn`, where the `fqdn` is an authorised domain for your tenancy.
687
+ comments (str):
688
+ Additional information about this VPN credential.
689
+ location_id (str):
690
+ Associate the VPN credential with an existing location.
691
+
692
+ Returns:
693
+ :obj:`Box`: The newly created VPN credential resource record.
694
+ """
695
+
696
+ # Generate a random PSK if not provided
697
+ if not pre_shared_key:
698
+ pre_shared_key = self.randomize_psk()
699
+
700
+ payload = {
701
+ "type": authentication_type,
702
+ "preSharedKey": pre_shared_key,
703
+ }
704
+
705
+ # Add location ID to payload if specified.
706
+ if kwargs.get("location_id"):
707
+ payload["location"] = {"id": kwargs.pop("location_id")}
708
+
709
+ # Add optional parameters to payload
710
+ for key, value in kwargs.items():
711
+ payload[snake_to_camel(key)] = value
712
+
713
+ response = self.rest.post("vpnCredentials", json=payload)
714
+ if isinstance(response, Response):
715
+ # this is only true when the creation failed (status code is not 2xx)
716
+ status_code = response.status_code
717
+ # Handle error response
718
+ raise Exception(
719
+ f"API call failed with status {status_code}: {response.json()}"
720
+ )
721
+ return response
722
+
723
+ def bulk_delete_vpn_credentials(self, credential_ids: list) -> int:
724
+ """
725
+ Bulk delete VPN credentials.
726
+
727
+ Args:
728
+ credential_ids (list):
729
+ List of credential IDs that will be deleted.
730
+
731
+ Returns:
732
+ :obj:`int`: Response code for operation.
733
+
734
+ Examples:
735
+ >>> zia.traffic.bulk_delete_vpn_credentials(['94963984', '97679232'])
736
+
737
+ """
738
+
739
+ payload = {"ids": credential_ids}
740
+
741
+ response = self.rest.post("vpnCredentials/bulkDelete", json=payload).status_code
742
+ if isinstance(response, Response):
743
+ # this is only true when the creation failed (status code is not 2xx)
744
+ status_code = response.status_code
745
+ # Handle error response
746
+ raise Exception(
747
+ f"API call failed with status {status_code}: {response.json()}"
748
+ )
749
+ return response
750
+
751
+ def get_vpn_credential(self, credential_id: str = None, fqdn: str = None) -> Box:
752
+ """
753
+ Get VPN credentials for the specified ID or fqdn.
754
+
755
+ Args:
756
+ credential_id (str, optional):
757
+ The unique identifier for the VPN credentials.
758
+ fqdn (str, optional):
759
+ The unique FQDN for the VPN credentials.
760
+
761
+ Returns:
762
+ :obj:`Box`: The resource record for the requested VPN credentials.
763
+
764
+ Examples:
765
+ >>> pprint(zia.traffic.get_vpn_credential('97679391'))
766
+
767
+ >>> pprint(zia.traffic.get_vpn_credential(fqdn='userid@fqdn'))
768
+
769
+ """
770
+ if credential_id and fqdn:
771
+ raise ValueError(
772
+ "TOO MANY ARGUMENTS: Expected either a credential_id or an fqdn. Both were provided."
773
+ )
774
+ elif fqdn:
775
+ credential = (
776
+ record
777
+ for record in self.list_vpn_credentials(search=fqdn)
778
+ if record.fqdn == fqdn
779
+ )
780
+ return next(credential, None)
781
+
782
+ return self.rest.get(f"vpnCredentials/{credential_id}")
783
+
784
+ def update_vpn_credential(self, credential_id: str, **kwargs) -> Box:
785
+ """
786
+ Update VPN credentials with the specified ID.
787
+
788
+ Args:
789
+ credential_id (str):
790
+ The unique identifier for the credential that will be updated.
791
+
792
+ Keyword Args:
793
+ pre_shared_key (str):
794
+ Pre-shared key. This is a required field for UFQDN and IP auth type.
795
+ comments (str):
796
+ Additional information about this VPN credential.
797
+ location_id (str):
798
+ The unique identifier for an existing location.
799
+
800
+ Returns:
801
+ :obj:`Box`: The newly updated VPN credential resource record.
802
+
803
+ Examples:
804
+ Add a comment:
805
+
806
+ >>> zia.traffic.update_vpn_credential('94963984',
807
+ ... comments='Adding a comment')
808
+
809
+ Update the pre-shared key:
810
+
811
+ >>> zia.traffic.update_vpn_credential('94963984',
812
+ ... pre_shared_key='MyNewInsecureKey',
813
+ ... comments='Pre-shared key rotated on 21 JUL 21')
814
+
815
+ """
816
+
817
+ # Cache the credential record
818
+ payload = convert_keys(self.get_vpn_credential(credential_id))
819
+
820
+ # Add location ID to payload if specified.
821
+ if kwargs.get("location_id"):
822
+ payload["location"] = {"id": kwargs.pop("location_id")}
823
+
824
+ # Add optional parameters to payload
825
+ for key, value in kwargs.items():
826
+ payload[snake_to_camel(key)] = value
827
+
828
+ response = self.rest.put(f"vpnCredentials/{credential_id}", json=payload)
829
+ if isinstance(response, Response) and not response.ok:
830
+ # Handle error response
831
+ raise Exception(
832
+ f"API call failed with status {response.status_code}: {response.json()}"
833
+ )
834
+
835
+ # Return the updated object
836
+ return self.get_vpn_credential(credential_id)
837
+
838
+ def delete_vpn_credential(self, credential_id: str) -> int:
839
+ """
840
+ Delete VPN credentials for the specified ID.
841
+
842
+ Args:
843
+ credential_id (str):
844
+ The unique identifier for the VPN credentials that will be deleted.
845
+
846
+ Returns:
847
+ :obj:`int`: Response code for the operation.
848
+
849
+ Examples:
850
+ >>> zia.traffic.delete_vpn_credential('97679391')
851
+
852
+ """
853
+ return self.rest.delete(f"vpnCredentials/{credential_id}").status_code