catocli 1.0.21__py3-none-any.whl → 2.0.1__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.

Potentially problematic release.


This version of catocli might be problematic. Click here for more details.

Files changed (139) hide show
  1. catocli/Utils/clidriver.py +112 -25
  2. catocli/Utils/profile_manager.py +188 -0
  3. catocli/Utils/version_checker.py +192 -0
  4. catocli/__init__.py +1 -1
  5. catocli/parsers/configure/__init__.py +115 -0
  6. catocli/parsers/configure/configure.py +307 -0
  7. catocli/parsers/custom/__init__.py +8 -0
  8. catocli/parsers/custom/export_rules/__init__.py +36 -0
  9. catocli/parsers/custom/export_rules/export_rules.py +361 -0
  10. catocli/parsers/custom/import_rules_to_tf/__init__.py +58 -0
  11. catocli/parsers/custom/import_rules_to_tf/import_rules_to_tf.py +577 -0
  12. catocli/parsers/mutation_admin_addAdmin/README.md +1 -1
  13. catocli/parsers/mutation_hardware/README.md +7 -0
  14. catocli/parsers/mutation_hardware/__init__.py +23 -0
  15. catocli/parsers/mutation_hardware_updateHardwareShipping/README.md +17 -0
  16. catocli/parsers/mutation_site_addBgpPeer/README.md +1 -1
  17. catocli/parsers/mutation_site_addNetworkRange/README.md +1 -1
  18. catocli/parsers/mutation_site_updateBgpPeer/README.md +1 -1
  19. catocli/parsers/mutation_site_updateNetworkRange/README.md +1 -1
  20. catocli/parsers/mutation_sites_addBgpPeer/README.md +1 -1
  21. catocli/parsers/mutation_sites_addNetworkRange/README.md +1 -1
  22. catocli/parsers/mutation_sites_updateBgpPeer/README.md +1 -1
  23. catocli/parsers/mutation_sites_updateNetworkRange/README.md +1 -1
  24. catocli/parsers/query_auditFeed/README.md +1 -1
  25. catocli/parsers/query_catalogs/README.md +19 -0
  26. catocli/parsers/query_catalogs/__init__.py +17 -0
  27. catocli/parsers/query_devices/README.md +19 -0
  28. catocli/parsers/query_devices/__init__.py +17 -0
  29. catocli/parsers/query_eventsFeed/README.md +1 -1
  30. catocli/parsers/query_hardware/README.md +17 -0
  31. catocli/parsers/query_hardware/__init__.py +17 -0
  32. catocli/parsers/query_sandbox/README.md +1 -1
  33. {catocli-1.0.21.dist-info → catocli-2.0.1.dist-info}/METADATA +1 -1
  34. {catocli-1.0.21.dist-info → catocli-2.0.1.dist-info}/RECORD +139 -114
  35. {catocli-1.0.21.dist-info → catocli-2.0.1.dist-info}/top_level.txt +1 -0
  36. graphql_client/api/call_api.py +4 -0
  37. graphql_client/api_client_types.py +4 -3
  38. graphql_client/configuration.py +2 -0
  39. models/mutation.admin.addAdmin.json +130 -0
  40. models/mutation.hardware.updateHardwareShipping.json +2506 -0
  41. models/mutation.policy.appTenantRestriction.addRule.json +11 -11
  42. models/mutation.policy.appTenantRestriction.createPolicyRevision.json +11 -11
  43. models/mutation.policy.appTenantRestriction.discardPolicyRevision.json +11 -11
  44. models/mutation.policy.appTenantRestriction.moveRule.json +11 -11
  45. models/mutation.policy.appTenantRestriction.publishPolicyRevision.json +11 -11
  46. models/mutation.policy.appTenantRestriction.removeRule.json +11 -11
  47. models/mutation.policy.appTenantRestriction.updatePolicy.json +11 -11
  48. models/mutation.policy.appTenantRestriction.updateRule.json +11 -11
  49. models/mutation.policy.dynamicIpAllocation.addRule.json +4 -4
  50. models/mutation.policy.dynamicIpAllocation.createPolicyRevision.json +4 -4
  51. models/mutation.policy.dynamicIpAllocation.discardPolicyRevision.json +4 -4
  52. models/mutation.policy.dynamicIpAllocation.moveRule.json +4 -4
  53. models/mutation.policy.dynamicIpAllocation.publishPolicyRevision.json +4 -4
  54. models/mutation.policy.dynamicIpAllocation.removeRule.json +4 -4
  55. models/mutation.policy.dynamicIpAllocation.updatePolicy.json +4 -4
  56. models/mutation.policy.dynamicIpAllocation.updateRule.json +4 -4
  57. models/mutation.policy.internetFirewall.addRule.json +63 -63
  58. models/mutation.policy.internetFirewall.createPolicyRevision.json +45 -45
  59. models/mutation.policy.internetFirewall.discardPolicyRevision.json +45 -45
  60. models/mutation.policy.internetFirewall.moveRule.json +45 -45
  61. models/mutation.policy.internetFirewall.publishPolicyRevision.json +45 -45
  62. models/mutation.policy.internetFirewall.removeRule.json +45 -45
  63. models/mutation.policy.internetFirewall.updatePolicy.json +45 -45
  64. models/mutation.policy.internetFirewall.updateRule.json +63 -63
  65. models/mutation.policy.remotePortFwd.addRule.json +5 -5
  66. models/mutation.policy.remotePortFwd.createPolicyRevision.json +5 -5
  67. models/mutation.policy.remotePortFwd.discardPolicyRevision.json +5 -5
  68. models/mutation.policy.remotePortFwd.moveRule.json +5 -5
  69. models/mutation.policy.remotePortFwd.publishPolicyRevision.json +5 -5
  70. models/mutation.policy.remotePortFwd.removeRule.json +5 -5
  71. models/mutation.policy.remotePortFwd.updatePolicy.json +5 -5
  72. models/mutation.policy.remotePortFwd.updateRule.json +5 -5
  73. models/mutation.policy.socketLan.addRule.json +3580 -125
  74. models/mutation.policy.socketLan.createPolicyRevision.json +3580 -125
  75. models/mutation.policy.socketLan.discardPolicyRevision.json +3580 -125
  76. models/mutation.policy.socketLan.moveRule.json +3580 -125
  77. models/mutation.policy.socketLan.publishPolicyRevision.json +3580 -125
  78. models/mutation.policy.socketLan.removeRule.json +3580 -125
  79. models/mutation.policy.socketLan.updatePolicy.json +3580 -125
  80. models/mutation.policy.socketLan.updateRule.json +3580 -125
  81. models/mutation.policy.wanFirewall.addRule.json +77 -77
  82. models/mutation.policy.wanFirewall.createPolicyRevision.json +59 -59
  83. models/mutation.policy.wanFirewall.discardPolicyRevision.json +59 -59
  84. models/mutation.policy.wanFirewall.moveRule.json +59 -59
  85. models/mutation.policy.wanFirewall.publishPolicyRevision.json +59 -59
  86. models/mutation.policy.wanFirewall.removeRule.json +59 -59
  87. models/mutation.policy.wanFirewall.updatePolicy.json +59 -59
  88. models/mutation.policy.wanFirewall.updateRule.json +77 -77
  89. models/mutation.policy.wanNetwork.addRule.json +49 -49
  90. models/mutation.policy.wanNetwork.createPolicyRevision.json +49 -49
  91. models/mutation.policy.wanNetwork.discardPolicyRevision.json +49 -49
  92. models/mutation.policy.wanNetwork.moveRule.json +49 -49
  93. models/mutation.policy.wanNetwork.publishPolicyRevision.json +49 -49
  94. models/mutation.policy.wanNetwork.removeRule.json +49 -49
  95. models/mutation.policy.wanNetwork.updatePolicy.json +49 -49
  96. models/mutation.policy.wanNetwork.updateRule.json +49 -49
  97. models/mutation.site.addBgpPeer.json +2812 -217
  98. models/mutation.site.addNetworkRange.json +114 -0
  99. models/mutation.site.addSocketSite.json +18 -0
  100. models/mutation.site.removeBgpPeer.json +667 -1
  101. models/mutation.site.updateBgpPeer.json +3152 -559
  102. models/mutation.site.updateNetworkRange.json +114 -0
  103. models/mutation.sites.addBgpPeer.json +2812 -217
  104. models/mutation.sites.addNetworkRange.json +114 -0
  105. models/mutation.sites.addSocketSite.json +18 -0
  106. models/mutation.sites.removeBgpPeer.json +667 -1
  107. models/mutation.sites.updateBgpPeer.json +3152 -559
  108. models/mutation.sites.updateNetworkRange.json +114 -0
  109. models/mutation.xdr.addStoryComment.json +2 -2
  110. models/mutation.xdr.analystFeedback.json +182 -42
  111. models/mutation.xdr.deleteStoryComment.json +2 -2
  112. models/query.accountMetrics.json +112 -0
  113. models/query.accountSnapshot.json +62 -0
  114. models/query.admin.json +46 -0
  115. models/query.admins.json +46 -0
  116. models/query.appStats.json +528 -0
  117. models/query.appStatsTimeSeries.json +396 -0
  118. models/query.auditFeed.json +273 -3336
  119. models/query.catalogs.json +9840 -0
  120. models/query.devices.json +15469 -0
  121. models/query.events.json +4606 -4318
  122. models/query.eventsFeed.json +1167 -1095
  123. models/query.eventsTimeSeries.json +3459 -3243
  124. models/query.hardware.json +5730 -0
  125. models/query.hardwareManagement.json +8 -2
  126. models/query.licensing.json +3 -3
  127. models/query.policy.json +3743 -298
  128. models/query.sandbox.json +6 -4
  129. models/query.site.json +1329 -4
  130. models/query.xdr.stories.json +182 -42
  131. models/query.xdr.story.json +182 -42
  132. schema/catolib.py +105 -28
  133. scripts/catolib.py +62 -0
  134. scripts/export_if_rules_to_json.py +188 -0
  135. scripts/export_wf_rules_to_json.py +111 -0
  136. scripts/import_wf_rules_to_tfstate.py +331 -0
  137. {catocli-1.0.21.dist-info → catocli-2.0.1.dist-info}/LICENSE +0 -0
  138. {catocli-1.0.21.dist-info → catocli-2.0.1.dist-info}/WHEEL +0 -0
  139. {catocli-1.0.21.dist-info → catocli-2.0.1.dist-info}/entry_points.txt +0 -0
schema/catolib.py CHANGED
@@ -503,8 +503,6 @@ def writeCliDriver(catoApiSchema):
503
503
  parsersIndex[operationNameAry[0]+"_"+operationNameAry[1]] = True
504
504
  parsers = list(parsersIndex.keys())
505
505
 
506
-
507
-
508
506
  cliDriverStr = """
509
507
  import os
510
508
  import argparse
@@ -513,15 +511,15 @@ import catocli
513
511
  from graphql_client import Configuration
514
512
  from graphql_client.api_client import ApiException
515
513
  from ..parsers.parserApiClient import get_help
514
+ from .profile_manager import get_profile_manager
515
+ from .version_checker import check_for_updates, force_check_updates
516
516
  import traceback
517
517
  import sys
518
518
  sys.path.insert(0, 'vendor')
519
519
  import urllib3
520
520
  urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
521
- if "CATO_TOKEN" not in os.environ:
522
- print("Missing authentication, please set the CATO_TOKEN environment variable with your api key.")
523
- exit()
524
- CATO_TOKEN = os.getenv("CATO_TOKEN")
521
+ # Initialize profile manager
522
+ profile_manager = get_profile_manager()
525
523
  CATO_DEBUG = bool(os.getenv("CATO_DEBUG", False))
526
524
  from ..parsers.raw import raw_parse
527
525
  from ..parsers.custom import custom_parse
@@ -531,12 +529,56 @@ from ..parsers.query_siteLocation import query_siteLocation_parse
531
529
  cliDriverStr += "from ..parsers."+parserName+" import "+parserName+"_parse\n"
532
530
 
533
531
  cliDriverStr += """
534
- configuration = Configuration()
535
- configuration.verify_ssl = False
536
- configuration.api_key["x-api-key"] = CATO_TOKEN
537
- configuration.host = "{}".format(catocli.__cato_host__)
538
- configuration.debug = CATO_DEBUG
539
- configuration.version = "{}".format(catocli.__version__)
532
+ def show_version_info(args, configuration=None):
533
+ print(f"catocli version {catocli.__version__}")
534
+
535
+ if not args.current_only:
536
+ if args.check_updates:
537
+ # Force check for updates
538
+ is_newer, latest_version, source = force_check_updates()
539
+ else:
540
+ # Regular check (uses cache)
541
+ is_newer, latest_version, source = check_for_updates(show_if_available=False)
542
+
543
+ if latest_version:
544
+ if is_newer:
545
+ print(f"Latest version: {latest_version} (from {source}) - UPDATE AVAILABLE!")
546
+ print()
547
+ print("To upgrade, run:")
548
+ print("pip install --upgrade catocli")
549
+ else:
550
+ print(f"Latest version: {latest_version} (from {source}) - You are up to date!")
551
+ else:
552
+ print("Unable to check for updates (check your internet connection)")
553
+ return [{"success": True, "current_version": catocli.__version__, "latest_version": latest_version if not args.current_only else None}]
554
+
555
+ def get_configuration():
556
+ configuration = Configuration()
557
+ configuration.verify_ssl = False
558
+ configuration.debug = CATO_DEBUG
559
+ configuration.version = "{}".format(catocli.__version__)
560
+
561
+ # Try to migrate from environment variables first
562
+ profile_manager.migrate_from_environment()
563
+
564
+ # Get credentials from profile
565
+ credentials = profile_manager.get_credentials()
566
+ if not credentials:
567
+ print("No Cato CLI profile configured.")
568
+ print("Run 'catocli configure set' to set up your credentials.")
569
+ exit(1)
570
+
571
+ if not credentials.get('cato_token') or not credentials.get('account_id'):
572
+ profile_name = profile_manager.get_current_profile()
573
+ print(f"Profile '{profile_name}' is missing required credentials.")
574
+ print(f"Run 'catocli configure set --profile {profile_name}' to update your credentials.")
575
+ exit(1)
576
+
577
+ configuration.api_key["x-api-key"] = credentials['cato_token']
578
+ configuration.host = credentials['endpoint']
579
+ configuration.accountID = credentials['account_id']
580
+
581
+ return configuration
540
582
 
541
583
  defaultReadmeStr = \"""
542
584
  The Cato CLI is a command-line interface tool designed to simplify the management and automation of Cato Networks’ configurations and operations.
@@ -547,7 +589,15 @@ https://github.com/catonetworks/cato-api-explorer
547
589
 
548
590
  parser = argparse.ArgumentParser(prog='catocli', usage='%(prog)s <operationType> <operationName> [options]', description=defaultReadmeStr)
549
591
  parser.add_argument('--version', action='version', version=catocli.__version__)
592
+ parser.add_argument('-H', '--header', action='append', dest='headers', help='Add custom headers in "Key: Value" format. Can be used multiple times.')
550
593
  subparsers = parser.add_subparsers()
594
+
595
+ # Version command - enhanced with update checking
596
+ version_parser = subparsers.add_parser('version', help='Show version information and check for updates')
597
+ version_parser.add_argument('--check-updates', action='store_true', help='Force check for updates (ignores cache)')
598
+ version_parser.add_argument('--current-only', action='store_true', help='Show only current version')
599
+ version_parser.set_defaults(func=show_version_info)
600
+
551
601
  custom_parsers = custom_parse(subparsers)
552
602
  raw_parsers = subparsers.add_parser('raw', help='Raw GraphQL', usage=get_help("raw"))
553
603
  raw_parser = raw_parse(raw_parsers)
@@ -563,24 +613,51 @@ mutation_subparsers = mutation_parser.add_subparsers(description='valid subcomma
563
613
 
564
614
  cliDriverStr += """
565
615
 
616
+ def parse_headers(header_strings):
617
+ headers = {}
618
+ if header_strings:
619
+ for header_string in header_strings:
620
+ if ':' not in header_string:
621
+ print(f"ERROR: Invalid header format '{header_string}'. Use 'Key: Value' format.")
622
+ exit(1)
623
+ key, value = header_string.split(':', 1)
624
+ headers[key.strip()] = value.strip()
625
+ return headers
626
+
566
627
  def main(args=None):
628
+ # Check if no arguments provided or help is requested
629
+ if args is None:
630
+ args = sys.argv[1:]
631
+
632
+ # Show version check when displaying help or when no command specified
633
+ if not args or '-h' in args or '--help' in args:
634
+ # Check for updates in background (non-blocking)
635
+ try:
636
+ check_for_updates(show_if_available=True)
637
+ except Exception:
638
+ # Don't let version check interfere with CLI operation
639
+ pass
640
+
567
641
  args = parser.parse_args(args=args)
568
642
  try:
569
- CATO_ACCOUNT_ID = os.getenv("CATO_ACCOUNT_ID")
570
- if args.func.__name__!="createRawRequest":
571
- if CATO_ACCOUNT_ID==None and args.accountID==None:
572
- print("Missing accountID, please specify an accountID:\\n")
573
- print('Option 1: Set the CATO_ACCOUNT_ID environment variable with the value of your account ID.')
574
- print('export CATO_ACCOUNT_ID="12345"\\n')
575
- print("Option 2: Override the accountID value as a cli argument, example:")
576
- print('catocli <operationType> <operationName> -accountID=12345 <json>')
577
- print("catocli query entityLookup -accountID=12345 '{\\\"type\\\":\\\"country\\\"}'\\n")
578
- exit()
579
- elif args.accountID!=None:
580
- configuration.accountID = args.accountID
581
- else:
582
- configuration.accountID = CATO_ACCOUNT_ID
583
- response = args.func(args, configuration)
643
+ # Skip authentication for configure commands
644
+ if hasattr(args, 'func') and hasattr(args.func, '__module__') and 'configure' in str(args.func.__module__):
645
+ response = args.func(args, None)
646
+ else:
647
+ # Get configuration from profiles
648
+ configuration = get_configuration()
649
+
650
+ # Parse custom headers if provided
651
+ if hasattr(args, 'headers') and args.headers:
652
+ custom_headers = parse_headers(args.headers)
653
+ configuration.custom_headers.update(custom_headers)
654
+ # Handle account ID override
655
+ if args.func.__name__ != "createRawRequest":
656
+ if hasattr(args, 'accountID') and args.accountID is not None:
657
+ # Command line override takes precedence
658
+ configuration.accountID = args.accountID
659
+ # Otherwise use the account ID from the profile (already set in get_configuration)
660
+ response = args.func(args, configuration)
584
661
 
585
662
  if type(response) == ApiException:
586
663
  print("ERROR! Status code: {}".format(response.status))
@@ -932,7 +1009,7 @@ def generateGraphqlPayload(variablesObj,operation,operationName):
932
1009
  queryStr += ") {\n" + renderArgsAndFields("", variablesObj, operation, operation["type"]["definition"], " ") + " }"
933
1010
  queryStr += indent + "\n}";
934
1011
  body = {
935
- "query":queryStr.replace("\n", " ").replace("\t", " ").replace(" ", " ").replace(" ", " ").replace(" ", " "),
1012
+ "query":queryStr.replace("\n", " ").replace("\t", " ").replace(" ", " ").replace(" ", " ").replace(" ", " "),
936
1013
  "variables":variablesObj,
937
1014
  "operationName":renderCamelCase(".".join(operationAry)),
938
1015
  }
scripts/catolib.py ADDED
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env python3
2
+ import json
3
+ import os
4
+ import sys
5
+ import subprocess
6
+ from typing import Any, Dict, List, Union
7
+
8
+ def should_strip_id(obj: Dict[str, Any]) -> bool:
9
+ if not isinstance(obj, dict):
10
+ return False
11
+
12
+ keys = set(obj.keys())
13
+ return keys == {'name', 'id'}
14
+
15
+ def strip_ids_recursive(obj: Union[Dict, List, Any]) -> Union[Dict, List, Any, None]:
16
+ if isinstance(obj, dict):
17
+ new_obj = {}
18
+ for key, value in obj.items():
19
+ ## Ignoring sections to retain id and name pair
20
+ if key == 'sections':
21
+ new_obj[key] = value
22
+ else:
23
+ processed_value = strip_ids_recursive(value)
24
+ # Skip keys where the value is an empty list
25
+ if processed_value != []:
26
+ new_obj[key] = processed_value
27
+ # Check if this object should have its id stripped
28
+ if should_strip_id(new_obj):
29
+ return {'name': new_obj['name']}
30
+ return new_obj
31
+
32
+ elif isinstance(obj, list):
33
+ # Recursively process all items in the list
34
+ processed_list = [strip_ids_recursive(item) for item in obj]
35
+ # Remove None values from the list (if any were removed)
36
+ processed_list = [item for item in processed_list if item is not None]
37
+ return processed_list
38
+
39
+ else:
40
+ # For primitive types (str, int, bool, None), return as-is
41
+ return obj
42
+
43
+ ## General purpose functions
44
+ def exec_cli(command):
45
+ result = None
46
+ try:
47
+ response = subprocess.run(command, shell=True, text=True, capture_output=True)
48
+ if response.returncode != 0:
49
+ print(f"Command failed with return code {response.returncode}")
50
+ print(f"stderr: {response.stderr}")
51
+ return None
52
+ result = json.loads(response.stdout)
53
+ except json.JSONDecodeError as e:
54
+ print(f"Failed to parse JSON response: {e}")
55
+ print(f"stdout: {response.stdout}")
56
+ print(f"stderr: {response.stderr}")
57
+ return None
58
+ except Exception as e:
59
+ print(f"Failed to execute command: {e}")
60
+ return None
61
+ return result
62
+
@@ -0,0 +1,188 @@
1
+ import sys
2
+ import csv
3
+ import subprocess
4
+ import json
5
+ import os
6
+ import argparse
7
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
8
+ from graphql_client.api.call_api import ApiClient, CallApi
9
+ from graphql_client import Configuration
10
+ from graphql_client.api_client import ApiException
11
+ import catocli
12
+
13
+ # Configuration variables
14
+ DESTINATION_DIR = "config_data"
15
+
16
+ def strip_ids_recursive(data):
17
+ """Recursively strip id attributes from data structure"""
18
+ if isinstance(data, dict):
19
+ return {k: strip_ids_recursive(v) for k, v in data.items() if k != 'id'}
20
+ elif isinstance(data, list):
21
+ return [strip_ids_recursive(item) for item in data]
22
+ else:
23
+ return data
24
+
25
+ def main():
26
+ # Parse command line arguments
27
+ parser = argparse.ArgumentParser(description='Export IFW rules to JSON')
28
+ parser.add_argument('-accountID', help='Account ID to export rules from', required=True)
29
+ parser.add_argument('--output-file-path', help='Full path including filename and extension for output file. If not specified, uses default: config_data/all_ifw_rules_and_sections_{account_id}.json')
30
+ parser.add_argument('-v', '--verbose', action='store_true', help='Verbose output')
31
+ args = parser.parse_args()
32
+
33
+ ACCOUNT_ID = args.accountID
34
+
35
+ # Setup API client configuration
36
+ if "CATO_TOKEN" not in os.environ:
37
+ print("Missing authentication, please set the CATO_TOKEN environment variable with your api key.")
38
+ sys.exit(1)
39
+
40
+ configuration = Configuration()
41
+ configuration.verify_ssl = False
42
+ configuration.api_key["x-api-key"] = os.getenv("CATO_TOKEN")
43
+ configuration.host = "{}".format(catocli.__cato_host__)
44
+ configuration.accountID = ACCOUNT_ID
45
+
46
+ # Set up output file path
47
+ if args.output_file_path:
48
+ # Use output file path if provided
49
+ output_file = args.output_file_path
50
+ destination_dir = os.path.dirname(output_file)
51
+ if args.verbose:
52
+ print(f"Using output file path: {output_file}")
53
+ else:
54
+ # Use default path and filename
55
+ destination_dir = 'config_data'
56
+ json_output_file = f"all_ifw_rules_and_sections_{ACCOUNT_ID}.json"
57
+ output_file = os.path.join(destination_dir, json_output_file)
58
+ if args.verbose:
59
+ print(f"Using default path: {output_file}")
60
+
61
+ # Create destination directory if it doesn't exist
62
+ if destination_dir and not os.path.exists(destination_dir):
63
+ if args.verbose:
64
+ print(f"Creating directory: {destination_dir}")
65
+ os.makedirs(destination_dir)
66
+ policyQuery = {
67
+ "query": "query policy ( $accountId:ID! ) { policy ( accountId:$accountId ) { internetFirewall { policy { enabled rules { audit { updatedTime updatedBy } rule { id name description index section { id name } enabled source { ip host { id name } site { id name } subnet ipRange { from to } globalIpRange { id name } networkInterface { id name } siteNetworkSubnet { id name } floatingSubnet { id name } user { id name } usersGroup { id name } group { id name } systemGroup { id name } } connectionOrigin country { id name } device { id name } deviceOS deviceAttributes { category type model manufacturer os osVersion } destination { application { id name } customApp { id name } appCategory { id name } customCategory { id name } sanctionedAppsCategory { id name } country { id name } domain fqdn ip subnet ipRange { from to } globalIpRange { id name } remoteAsn containers { fqdnContainer { id name } ipAddressRangeContainer { id name } } } service { standard { id name } custom { port portRange { from to } protocol } } action tracking { event { enabled } alert { enabled frequency subscriptionGroup { id name } webhook { id name } mailingList { id name } } } schedule { activeOn customTimeframePolicySchedule: customTimeframe { from to } customRecurringPolicySchedule: customRecurring { from to days } } exceptions { name source { ip host { id name } site { id name } subnet ipRange { from to } globalIpRange { id name } networkInterface { id name } siteNetworkSubnet { id name } floatingSubnet { id name } user { id name } usersGroup { id name } group { id name } systemGroup { id name } } deviceOS country { id name } device { id name } deviceAttributes { category type model manufacturer os osVersion } destination { application { id name } customApp { id name } appCategory { id name } customCategory { id name } sanctionedAppsCategory { id name } country { id name } domain fqdn ip subnet ipRange { from to } globalIpRange { id name } remoteAsn containers { fqdnContainer { id name } ipAddressRangeContainer { id name } } } service { standard { id name } custom { port portRangeCustomService: portRange { from to } protocol } } connectionOrigin } } properties } sections { audit { updatedTime updatedBy } section { id name } properties } audit { publishedTime publishedBy } revision { id name description changes createdTime updatedTime } } } } }",
68
+ "variables": {
69
+ "accountId": ACCOUNT_ID
70
+ },
71
+ "operationName": "policy"
72
+ }
73
+ print(f"Retrieving all IFW rules and sections for account {ACCOUNT_ID}...")
74
+
75
+ # Create API client instance
76
+ instance = CallApi(ApiClient(configuration))
77
+
78
+ # Create params object for the API call
79
+ params = {
80
+ 'v': False, # verbose mode
81
+ 'f': 'json', # format
82
+ 'p': False, # pretty print
83
+ 't': False # test mode
84
+ }
85
+
86
+ try:
87
+ # Call the API directly
88
+ response = instance.call_api(policyQuery, params)
89
+ allIfwRules = response[0] if response else {}
90
+
91
+ if not allIfwRules or 'data' not in allIfwRules:
92
+ print("ERROR: Failed to retrieve data from API")
93
+ sys.exit(1)
94
+
95
+ except ApiException as e:
96
+ print(f"ERROR: API call failed - {e}")
97
+ sys.exit(1)
98
+ except Exception as e:
99
+ print(f"ERROR: Unexpected error - {e}")
100
+ sys.exit(1)
101
+
102
+ # First, preserve section IDs before stripping them
103
+ section_id_map = {}
104
+ sections_with_ids = allIfwRules['data']['policy']['internetFirewall']['policy']['sections']
105
+ for section_data in sections_with_ids:
106
+ section_id_map[section_data['section']['name']] = section_data['section']['id']
107
+
108
+ # Get the first section ID for section_to_start_after_id
109
+ section_to_start_after_id = None
110
+ if len(sections_with_ids) > 0:
111
+ section_to_start_after_id = sections_with_ids[0]['section']['id']
112
+ print(f"Excluding first section with system rule: {sections_with_ids[0]['section']['name']}")
113
+
114
+ ## Processing data to strip id attributes
115
+ processed_data = strip_ids_recursive(allIfwRules)
116
+
117
+ ## Filter out rules with properties[0]=="SYSTEM"
118
+ filtered_rules = []
119
+ for rule_data in processed_data['data']['policy']['internetFirewall']['policy']['rules']:
120
+ rule_properties = rule_data.get('properties', [])
121
+ # Skip rules where the first property is "SYSTEM"
122
+ if rule_properties and rule_properties[0] == "SYSTEM":
123
+ print(f"Excluding SYSTEM rule: {rule_data['rule']['name']}")
124
+ else:
125
+ filtered_rules.append(rule_data)
126
+ processed_data['data']['policy']['internetFirewall']['policy']['rules'] = filtered_rules
127
+ # rules = filtered_rules
128
+
129
+ # Add index_in_section to each rule
130
+ # Group rules by section and add index_in_section
131
+ section_counters = {}
132
+ for rule_data in processed_data['data']['policy']['internetFirewall']['policy']['rules']:
133
+ section_name = rule_data['rule']['section']['name']
134
+ if section_name not in section_counters:
135
+ section_counters[section_name] = 0
136
+ section_counters[section_name] += 1
137
+ rule_data['rule']['index_in_section'] = section_counters[section_name]
138
+
139
+ # Create rules_in_sections array
140
+ rules_in_sections = []
141
+ for rule_data in processed_data['data']['policy']['internetFirewall']['policy']['rules']:
142
+ rule_info = rule_data['rule']
143
+ rules_in_sections.append({
144
+ "index_in_section": rule_info['index_in_section'],
145
+ "section_name": rule_info['section']['name'],
146
+ "rule_name": rule_info['name']
147
+ })
148
+ rule_info.pop("index_in_section", None)
149
+ rule_info.pop("index", None)
150
+ rule_info["enabled"] = True
151
+
152
+ # Add rules_in_sections to the policy structure
153
+ processed_data['data']['policy']['internetFirewall']['policy']['rules_in_sections'] = rules_in_sections
154
+
155
+ # Reformat sections array to have index, section_id and section_name structure
156
+ # Exclude the first section from export
157
+ processed_sections = []
158
+ # Add first section containing reserved SYSTEM rules as section_to_start_after_id
159
+ for index, section_data in enumerate(processed_data['data']['policy']['internetFirewall']['policy']['sections']):
160
+ if index == 0:
161
+ # Skip the first section which contains reserved SYSTEM rules
162
+ continue
163
+ else:
164
+ processed_sections.append({
165
+ "section_index": index,
166
+ "section_name": section_data['section']['name']
167
+ })
168
+
169
+ # Add preserved section IDs and section_to_start_after_id
170
+ processed_data['data']['policy']['internetFirewall']['policy']['section_ids'] = section_id_map
171
+ if section_to_start_after_id:
172
+ processed_data['data']['policy']['internetFirewall']['policy']['section_to_start_after_id'] = section_to_start_after_id
173
+
174
+ if len(sections_with_ids) > 0:
175
+ print(f"Excluded first section: {sections_with_ids[0]['section']['name']} containing reserved SYSTEM rules")
176
+
177
+ # Replace the original sections array with the reformatted one
178
+ processed_data['data']['policy']['internetFirewall']['policy']['sections'] = processed_sections
179
+
180
+ # Write the processed data to the new file
181
+ with open(output_file, 'w', encoding='utf-8') as f:
182
+ json.dump(processed_data, f, indent=4, ensure_ascii=False)
183
+ print(f"Successfully created {output_file}")
184
+
185
+
186
+ if __name__ == "__main__":
187
+ main()
188
+
@@ -0,0 +1,111 @@
1
+ import sys
2
+ import csv
3
+ import subprocess
4
+ import json
5
+ import os
6
+ import argparse
7
+ import catolib
8
+
9
+ # Configuration variables
10
+ DESTINATION_DIR = "config_data"
11
+
12
+ def main():
13
+ # Parse command line arguments
14
+ parser = argparse.ArgumentParser(description='Export WAN rules to JSON')
15
+ parser.add_argument('account_id', help='Account ID to export rules from')
16
+ args = parser.parse_args()
17
+
18
+ ACCOUNT_ID = args.account_id
19
+ JSON_OUTPUT_FILE = f"all_wf_rules_and_sections_{ACCOUNT_ID}.json"
20
+ # Create destination directory if it doesn't exist
21
+ if not os.path.exists(DESTINATION_DIR):
22
+ print(f"Creating directory: {DESTINATION_DIR}")
23
+ os.makedirs(DESTINATION_DIR)
24
+
25
+ # Set full file paths
26
+ output_file = os.path.join(DESTINATION_DIR, JSON_OUTPUT_FILE)
27
+ policyQuery = {
28
+ "query": "query policy ( $accountId:ID! ) { policy ( accountId:$accountId ) { wanFirewall { policy { enabled rules { audit { updatedTime updatedBy } rule { id name description index section { id name } enabled source { host { id name } site { id name } subnet ip ipRange { from to } globalIpRange { id name } networkInterface { id name } siteNetworkSubnet { id name } floatingSubnet { id name } user { id name } usersGroup { id name } group { id name } systemGroup { id name } } connectionOrigin country { id name } device { id name } deviceOS deviceAttributes { category type model manufacturer os osVersion } destination { host { id name } site { id name } subnet ip ipRange { from to } globalIpRange { id name } networkInterface { id name } siteNetworkSubnet { id name } floatingSubnet { id name } user { id name } usersGroup { id name } group { id name } systemGroup { id name } } application { application { id name } appCategory { id name } customApp { id name } customCategory { id name } sanctionedAppsCategory { id name } domain fqdn ip subnet ipRange { from to } globalIpRange { id name } } service { standard { id name } custom { port portRange { from to } protocol } } action tracking { event { enabled } alert { enabled frequency subscriptionGroup { id name } webhook { id name } mailingList { id name } } } schedule { activeOn customTimeframePolicySchedule: customTimeframe { from to } customRecurringPolicySchedule: customRecurring { from to days } } direction exceptions { name source { host { id name } site { id name } subnet ip ipRange { from to } globalIpRange { id name } networkInterface { id name } siteNetworkSubnet { id name } floatingSubnet { id name } user { id name } usersGroup { id name } group { id name } systemGroup { id name } } deviceOS destination { host { id name } site { id name } subnet ip ipRange { from to } globalIpRange { id name } networkInterface { id name } siteNetworkSubnet { id name } floatingSubnet { id name } user { id name } usersGroup { id name } group { id name } systemGroup { id name } } country { id name } device { id name } deviceAttributes { category type model manufacturer os osVersion } application { application { id name } appCategory { id name } customApp { id name } customCategory { id name } sanctionedAppsCategory { id name } domain fqdn ip subnet ipRange { from to } globalIpRange { id name } } service { standard { id name } custom { port portRangeCustomService: portRange { from to } protocol } } connectionOrigin direction } } properties } sections { audit { updatedTime updatedBy } section { id name } properties } audit { publishedTime publishedBy } revision { id name description changes createdTime updatedTime } } } } }",
29
+ "variables": {
30
+ "accountId": ACCOUNT_ID
31
+ },
32
+ "operationName": "policy"
33
+ }
34
+ catocliCommand = "catocli raw '"+json.dumps(policyQuery)+"'"
35
+ print(f"Retrieving all WAN rules and sections for account {ACCOUNT_ID}...")
36
+ allWanRules = catolib.exec_cli(catocliCommand)
37
+
38
+ ## Processing data to strip id attributes
39
+ processed_data = catolib.strip_ids_recursive(allWanRules)
40
+
41
+ ## Preserving section IDs index by section name
42
+ section_id_map = {}
43
+ if 'data' in allWanRules and 'policy' in allWanRules['data'] and 'wanFirewall' in allWanRules['data']['policy']:
44
+ for section_data in allWanRules['data']['policy']['wanFirewall']['policy']['sections']:
45
+ section_id_map[section_data['section']['name']] = section_data['section']['id']
46
+ processed_data['data']['policy']['wanFirewall']['policy']['section_ids'] = section_id_map
47
+
48
+ ## Filter out rules with properties[0]=="SYSTEM"
49
+ filtered_rules = []
50
+ for rule_data in processed_data['data']['policy']['wanFirewall']['policy']['rules']:
51
+ rule_properties = rule_data.get('properties', [])
52
+ # Skip rules where the first property is "SYSTEM"
53
+ if rule_properties and rule_properties[0] == "SYSTEM":
54
+ print(f"Excluding SYSTEM rule: {rule_data['rule']['name']}")
55
+ else:
56
+ filtered_rules.append(rule_data)
57
+ processed_data['data']['policy']['wanFirewall']['policy']['rules'] = filtered_rules
58
+ # rules = filtered_rules
59
+
60
+ # Add index_in_section to each rule
61
+ # Group rules by section and add index_in_section
62
+ section_counters = {}
63
+ for rule_data in processed_data['data']['policy']['wanFirewall']['policy']['rules']:
64
+ section_name = rule_data['rule']['section']['name']
65
+ if section_name not in section_counters:
66
+ section_counters[section_name] = 0
67
+ section_counters[section_name] += 1
68
+ rule_data['rule']['index_in_section'] = section_counters[section_name]
69
+
70
+ # Create rules_in_sections array
71
+ rules_in_sections = []
72
+ for rule_data in processed_data['data']['policy']['wanFirewall']['policy']['rules']:
73
+ rule_info = rule_data['rule']
74
+ rules_in_sections.append({
75
+ "index_in_section": rule_info['index_in_section'],
76
+ "section_name": rule_info['section']['name'],
77
+ "rule_name": rule_info['name']
78
+ })
79
+ rule_info.pop("index_in_section", None)
80
+ rule_info.pop("index", None)
81
+ rule_info["enabled"] = True
82
+
83
+ # Add rules_in_sections to the policy structure
84
+ processed_data['data']['policy']['wanFirewall']['policy']['rules_in_sections'] = rules_in_sections
85
+
86
+ # Reformat sections array to have index, section_id and section_name structure
87
+ # Exclude the first section from export, excluding first section
88
+
89
+ processed_sections = []
90
+ # Add first section containing reserved SYSTEM rules as section_to_start_after_id
91
+ for index, section_data in enumerate(processed_data['data']['policy']['wanFirewall']['policy']['sections']):
92
+ processed_sections.append({
93
+ "section_index": index,
94
+ "section_name": section_data['section']['name']
95
+ })
96
+
97
+ if len(processed_data['data']['policy']['wanFirewall']['policy']['sections']) > 0:
98
+ print(f"Excluded first section: {processed_data['data']['policy']['wanFirewall']['policy']['sections'][0]['section']['name']} containing reserved SYSTEM rules")
99
+
100
+ # Replace the original sections array with the reformatted one
101
+ processed_data['data']['policy']['wanFirewall']['policy']['sections'] = processed_sections
102
+
103
+ # Write the processed data to the new file
104
+ with open(output_file, 'w', encoding='utf-8') as f:
105
+ json.dump(processed_data, f, indent=4, ensure_ascii=False)
106
+ print(f"Successfully created {output_file}")
107
+
108
+
109
+ if __name__ == "__main__":
110
+ main()
111
+