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.
- catocli/Utils/clidriver.py +112 -25
- catocli/Utils/profile_manager.py +188 -0
- catocli/Utils/version_checker.py +192 -0
- catocli/__init__.py +1 -1
- catocli/parsers/configure/__init__.py +115 -0
- catocli/parsers/configure/configure.py +307 -0
- catocli/parsers/custom/__init__.py +8 -0
- catocli/parsers/custom/export_rules/__init__.py +36 -0
- catocli/parsers/custom/export_rules/export_rules.py +361 -0
- catocli/parsers/custom/import_rules_to_tf/__init__.py +58 -0
- catocli/parsers/custom/import_rules_to_tf/import_rules_to_tf.py +577 -0
- catocli/parsers/mutation_admin_addAdmin/README.md +1 -1
- catocli/parsers/mutation_hardware/README.md +7 -0
- catocli/parsers/mutation_hardware/__init__.py +23 -0
- catocli/parsers/mutation_hardware_updateHardwareShipping/README.md +17 -0
- catocli/parsers/mutation_site_addBgpPeer/README.md +1 -1
- catocli/parsers/mutation_site_addNetworkRange/README.md +1 -1
- catocli/parsers/mutation_site_updateBgpPeer/README.md +1 -1
- catocli/parsers/mutation_site_updateNetworkRange/README.md +1 -1
- catocli/parsers/mutation_sites_addBgpPeer/README.md +1 -1
- catocli/parsers/mutation_sites_addNetworkRange/README.md +1 -1
- catocli/parsers/mutation_sites_updateBgpPeer/README.md +1 -1
- catocli/parsers/mutation_sites_updateNetworkRange/README.md +1 -1
- catocli/parsers/query_auditFeed/README.md +1 -1
- catocli/parsers/query_catalogs/README.md +19 -0
- catocli/parsers/query_catalogs/__init__.py +17 -0
- catocli/parsers/query_devices/README.md +19 -0
- catocli/parsers/query_devices/__init__.py +17 -0
- catocli/parsers/query_eventsFeed/README.md +1 -1
- catocli/parsers/query_hardware/README.md +17 -0
- catocli/parsers/query_hardware/__init__.py +17 -0
- catocli/parsers/query_sandbox/README.md +1 -1
- {catocli-1.0.21.dist-info → catocli-2.0.1.dist-info}/METADATA +1 -1
- {catocli-1.0.21.dist-info → catocli-2.0.1.dist-info}/RECORD +139 -114
- {catocli-1.0.21.dist-info → catocli-2.0.1.dist-info}/top_level.txt +1 -0
- graphql_client/api/call_api.py +4 -0
- graphql_client/api_client_types.py +4 -3
- graphql_client/configuration.py +2 -0
- models/mutation.admin.addAdmin.json +130 -0
- models/mutation.hardware.updateHardwareShipping.json +2506 -0
- models/mutation.policy.appTenantRestriction.addRule.json +11 -11
- models/mutation.policy.appTenantRestriction.createPolicyRevision.json +11 -11
- models/mutation.policy.appTenantRestriction.discardPolicyRevision.json +11 -11
- models/mutation.policy.appTenantRestriction.moveRule.json +11 -11
- models/mutation.policy.appTenantRestriction.publishPolicyRevision.json +11 -11
- models/mutation.policy.appTenantRestriction.removeRule.json +11 -11
- models/mutation.policy.appTenantRestriction.updatePolicy.json +11 -11
- models/mutation.policy.appTenantRestriction.updateRule.json +11 -11
- models/mutation.policy.dynamicIpAllocation.addRule.json +4 -4
- models/mutation.policy.dynamicIpAllocation.createPolicyRevision.json +4 -4
- models/mutation.policy.dynamicIpAllocation.discardPolicyRevision.json +4 -4
- models/mutation.policy.dynamicIpAllocation.moveRule.json +4 -4
- models/mutation.policy.dynamicIpAllocation.publishPolicyRevision.json +4 -4
- models/mutation.policy.dynamicIpAllocation.removeRule.json +4 -4
- models/mutation.policy.dynamicIpAllocation.updatePolicy.json +4 -4
- models/mutation.policy.dynamicIpAllocation.updateRule.json +4 -4
- models/mutation.policy.internetFirewall.addRule.json +63 -63
- models/mutation.policy.internetFirewall.createPolicyRevision.json +45 -45
- models/mutation.policy.internetFirewall.discardPolicyRevision.json +45 -45
- models/mutation.policy.internetFirewall.moveRule.json +45 -45
- models/mutation.policy.internetFirewall.publishPolicyRevision.json +45 -45
- models/mutation.policy.internetFirewall.removeRule.json +45 -45
- models/mutation.policy.internetFirewall.updatePolicy.json +45 -45
- models/mutation.policy.internetFirewall.updateRule.json +63 -63
- models/mutation.policy.remotePortFwd.addRule.json +5 -5
- models/mutation.policy.remotePortFwd.createPolicyRevision.json +5 -5
- models/mutation.policy.remotePortFwd.discardPolicyRevision.json +5 -5
- models/mutation.policy.remotePortFwd.moveRule.json +5 -5
- models/mutation.policy.remotePortFwd.publishPolicyRevision.json +5 -5
- models/mutation.policy.remotePortFwd.removeRule.json +5 -5
- models/mutation.policy.remotePortFwd.updatePolicy.json +5 -5
- models/mutation.policy.remotePortFwd.updateRule.json +5 -5
- models/mutation.policy.socketLan.addRule.json +3580 -125
- models/mutation.policy.socketLan.createPolicyRevision.json +3580 -125
- models/mutation.policy.socketLan.discardPolicyRevision.json +3580 -125
- models/mutation.policy.socketLan.moveRule.json +3580 -125
- models/mutation.policy.socketLan.publishPolicyRevision.json +3580 -125
- models/mutation.policy.socketLan.removeRule.json +3580 -125
- models/mutation.policy.socketLan.updatePolicy.json +3580 -125
- models/mutation.policy.socketLan.updateRule.json +3580 -125
- models/mutation.policy.wanFirewall.addRule.json +77 -77
- models/mutation.policy.wanFirewall.createPolicyRevision.json +59 -59
- models/mutation.policy.wanFirewall.discardPolicyRevision.json +59 -59
- models/mutation.policy.wanFirewall.moveRule.json +59 -59
- models/mutation.policy.wanFirewall.publishPolicyRevision.json +59 -59
- models/mutation.policy.wanFirewall.removeRule.json +59 -59
- models/mutation.policy.wanFirewall.updatePolicy.json +59 -59
- models/mutation.policy.wanFirewall.updateRule.json +77 -77
- models/mutation.policy.wanNetwork.addRule.json +49 -49
- models/mutation.policy.wanNetwork.createPolicyRevision.json +49 -49
- models/mutation.policy.wanNetwork.discardPolicyRevision.json +49 -49
- models/mutation.policy.wanNetwork.moveRule.json +49 -49
- models/mutation.policy.wanNetwork.publishPolicyRevision.json +49 -49
- models/mutation.policy.wanNetwork.removeRule.json +49 -49
- models/mutation.policy.wanNetwork.updatePolicy.json +49 -49
- models/mutation.policy.wanNetwork.updateRule.json +49 -49
- models/mutation.site.addBgpPeer.json +2812 -217
- models/mutation.site.addNetworkRange.json +114 -0
- models/mutation.site.addSocketSite.json +18 -0
- models/mutation.site.removeBgpPeer.json +667 -1
- models/mutation.site.updateBgpPeer.json +3152 -559
- models/mutation.site.updateNetworkRange.json +114 -0
- models/mutation.sites.addBgpPeer.json +2812 -217
- models/mutation.sites.addNetworkRange.json +114 -0
- models/mutation.sites.addSocketSite.json +18 -0
- models/mutation.sites.removeBgpPeer.json +667 -1
- models/mutation.sites.updateBgpPeer.json +3152 -559
- models/mutation.sites.updateNetworkRange.json +114 -0
- models/mutation.xdr.addStoryComment.json +2 -2
- models/mutation.xdr.analystFeedback.json +182 -42
- models/mutation.xdr.deleteStoryComment.json +2 -2
- models/query.accountMetrics.json +112 -0
- models/query.accountSnapshot.json +62 -0
- models/query.admin.json +46 -0
- models/query.admins.json +46 -0
- models/query.appStats.json +528 -0
- models/query.appStatsTimeSeries.json +396 -0
- models/query.auditFeed.json +273 -3336
- models/query.catalogs.json +9840 -0
- models/query.devices.json +15469 -0
- models/query.events.json +4606 -4318
- models/query.eventsFeed.json +1167 -1095
- models/query.eventsTimeSeries.json +3459 -3243
- models/query.hardware.json +5730 -0
- models/query.hardwareManagement.json +8 -2
- models/query.licensing.json +3 -3
- models/query.policy.json +3743 -298
- models/query.sandbox.json +6 -4
- models/query.site.json +1329 -4
- models/query.xdr.stories.json +182 -42
- models/query.xdr.story.json +182 -42
- schema/catolib.py +105 -28
- scripts/catolib.py +62 -0
- scripts/export_if_rules_to_json.py +188 -0
- scripts/export_wf_rules_to_json.py +111 -0
- scripts/import_wf_rules_to_tfstate.py +331 -0
- {catocli-1.0.21.dist-info → catocli-2.0.1.dist-info}/LICENSE +0 -0
- {catocli-1.0.21.dist-info → catocli-2.0.1.dist-info}/WHEEL +0 -0
- {catocli-1.0.21.dist-info → catocli-2.0.1.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
import sys
|
|
4
|
+
from graphql_client.api.call_api import ApiClient, CallApi
|
|
5
|
+
from graphql_client.api_client import ApiException
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def strip_ids_recursive(data):
|
|
9
|
+
"""Recursively strip id attributes from data structure, but only from objects that contain only 'id' and 'name' keys"""
|
|
10
|
+
try:
|
|
11
|
+
if isinstance(data, dict):
|
|
12
|
+
# Check if this dict should have its 'id' stripped
|
|
13
|
+
# Only strip 'id' if the object contains only 'id' and 'name' keys
|
|
14
|
+
dict_keys = set(data.keys())
|
|
15
|
+
should_strip_id = dict_keys == {'id', 'name'} or dict_keys == {'name', 'id'}
|
|
16
|
+
|
|
17
|
+
result = {}
|
|
18
|
+
for k, v in data.items():
|
|
19
|
+
if k == 'id' and should_strip_id:
|
|
20
|
+
# Skip this 'id' key only if this object contains only id and name
|
|
21
|
+
continue
|
|
22
|
+
else:
|
|
23
|
+
# Keep the key and recursively process the value
|
|
24
|
+
result[k] = strip_ids_recursive(v)
|
|
25
|
+
return result
|
|
26
|
+
elif isinstance(data, list):
|
|
27
|
+
return [strip_ids_recursive(item) for item in data]
|
|
28
|
+
else:
|
|
29
|
+
return data
|
|
30
|
+
except Exception as e:
|
|
31
|
+
print(f"Error in strip_ids_recursive: {e}, data type: {type(data)}, data: {str(data)[:100]}")
|
|
32
|
+
raise
|
|
33
|
+
|
|
34
|
+
def export_if_rules_to_json(args, configuration):
|
|
35
|
+
"""
|
|
36
|
+
Export Internet Firewall rules to JSON format
|
|
37
|
+
Adapted from scripts/export_if_rules_to_json.py
|
|
38
|
+
"""
|
|
39
|
+
try:
|
|
40
|
+
# Get account ID from args, configuration, or environment variable
|
|
41
|
+
account_id = None
|
|
42
|
+
if hasattr(args, 'accountID') and args.accountID:
|
|
43
|
+
account_id = args.accountID
|
|
44
|
+
elif hasattr(configuration, 'accountID') and configuration.accountID:
|
|
45
|
+
account_id = configuration.accountID
|
|
46
|
+
else:
|
|
47
|
+
account_id = os.getenv('CATO_ACCOUNT_ID')
|
|
48
|
+
|
|
49
|
+
if not account_id:
|
|
50
|
+
raise ValueError("Account ID is required. Provide it using the -accountID flag or set CATO_ACCOUNT_ID environment variable.")
|
|
51
|
+
|
|
52
|
+
# Set up output file path
|
|
53
|
+
if hasattr(args, 'output_file_path') and args.output_file_path:
|
|
54
|
+
# Use output file path if provided
|
|
55
|
+
output_file = args.output_file_path
|
|
56
|
+
destination_dir = os.path.dirname(output_file)
|
|
57
|
+
if hasattr(args, 'verbose') and args.verbose:
|
|
58
|
+
print(f"Using output file path: {output_file}")
|
|
59
|
+
else:
|
|
60
|
+
# Use default path and filename
|
|
61
|
+
destination_dir = 'config_data'
|
|
62
|
+
json_output_file = f"all_ifw_rules_and_sections_{account_id}.json"
|
|
63
|
+
output_file = os.path.join(destination_dir, json_output_file)
|
|
64
|
+
if hasattr(args, 'verbose') and args.verbose:
|
|
65
|
+
print(f"Using default path: {output_file}")
|
|
66
|
+
|
|
67
|
+
# Create destination directory if it doesn't exist
|
|
68
|
+
if destination_dir and not os.path.exists(destination_dir):
|
|
69
|
+
if hasattr(args, 'verbose') and args.verbose:
|
|
70
|
+
print(f"Creating directory: {destination_dir}")
|
|
71
|
+
os.makedirs(destination_dir)
|
|
72
|
+
|
|
73
|
+
# Define the GraphQL query
|
|
74
|
+
policy_query = {
|
|
75
|
+
"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 } } } } }",
|
|
76
|
+
"variables": {
|
|
77
|
+
"accountId": account_id
|
|
78
|
+
},
|
|
79
|
+
"operationName": "policy"
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
# Create API client instance with params
|
|
83
|
+
instance = CallApi(ApiClient(configuration))
|
|
84
|
+
params = {
|
|
85
|
+
'v': hasattr(args, 'verbose') and args.verbose, # verbose mode
|
|
86
|
+
'f': 'json', # format
|
|
87
|
+
'p': False, # pretty print
|
|
88
|
+
't': False # test mode
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
# Call the API directly
|
|
93
|
+
# NOTE: The API client (graphql_client/api_client_types.py lines 106-108)
|
|
94
|
+
# automatically prints error responses and exits on GraphQL errors.
|
|
95
|
+
# This means our custom error handling below may not be reached if there are GraphQL errors.
|
|
96
|
+
response = instance.call_api(policy_query, params)
|
|
97
|
+
all_ifw_rules = response[0] if response else {}
|
|
98
|
+
|
|
99
|
+
# Show raw API response in verbose mode
|
|
100
|
+
if hasattr(args, 'verbose') and args.verbose:
|
|
101
|
+
print("\n" + "=" * 80)
|
|
102
|
+
print("RAW API RESPONSE:")
|
|
103
|
+
print("=" * 80)
|
|
104
|
+
print(json.dumps(all_ifw_rules, indent=2))
|
|
105
|
+
print("=" * 80 + "\n")
|
|
106
|
+
|
|
107
|
+
# Check for GraphQL errors first (may not be reached due to API client behavior)
|
|
108
|
+
if 'errors' in all_ifw_rules:
|
|
109
|
+
error_messages = [error.get('message', 'Unknown error') for error in all_ifw_rules['errors']]
|
|
110
|
+
raise Exception(f"API returned errors: {', '.join(error_messages)}")
|
|
111
|
+
|
|
112
|
+
if not all_ifw_rules or 'data' not in all_ifw_rules:
|
|
113
|
+
raise ValueError("Failed to retrieve data from API")
|
|
114
|
+
|
|
115
|
+
except ApiException as e:
|
|
116
|
+
raise Exception(f"API call failed - {e}")
|
|
117
|
+
except Exception as e:
|
|
118
|
+
raise Exception(f"Unexpected error during API call - {e}")
|
|
119
|
+
|
|
120
|
+
# IMPORTANT: Preserve section IDs BEFORE stripping them
|
|
121
|
+
section_id_map = {}
|
|
122
|
+
section_to_start_after_id = None
|
|
123
|
+
sections_with_ids = all_ifw_rules['data']['policy']['internetFirewall']['policy']['sections']
|
|
124
|
+
for index, section_data in enumerate(sections_with_ids):
|
|
125
|
+
section_name = section_data['section']['name']
|
|
126
|
+
section_id = section_data['section']['id']
|
|
127
|
+
if index == 0:
|
|
128
|
+
section_to_start_after_id = section_id
|
|
129
|
+
else:
|
|
130
|
+
section_id_map[section_name] = section_id
|
|
131
|
+
|
|
132
|
+
# Processing data to strip id attributes
|
|
133
|
+
processed_data = strip_ids_recursive(all_ifw_rules)
|
|
134
|
+
|
|
135
|
+
# Filter out rules with properties[0]=="SYSTEM"
|
|
136
|
+
filtered_rules = []
|
|
137
|
+
for rule_data in processed_data['data']['policy']['internetFirewall']['policy']['rules']:
|
|
138
|
+
rule_properties = rule_data.get('properties', [])
|
|
139
|
+
# Skip rules where the first property is "SYSTEM"
|
|
140
|
+
if rule_properties and rule_properties[0] == "SYSTEM":
|
|
141
|
+
if hasattr(args, 'verbose') and args.verbose:
|
|
142
|
+
print(f"Excluding SYSTEM rule: {rule_data['rule']['name']}")
|
|
143
|
+
else:
|
|
144
|
+
filtered_rules.append(rule_data)
|
|
145
|
+
|
|
146
|
+
processed_data['data']['policy']['internetFirewall']['policy']['rules'] = filtered_rules
|
|
147
|
+
|
|
148
|
+
# Add index_in_section to each rule
|
|
149
|
+
# Group rules by section and add index_in_section
|
|
150
|
+
section_counters = {}
|
|
151
|
+
for rule_data in processed_data['data']['policy']['internetFirewall']['policy']['rules']:
|
|
152
|
+
section_name = rule_data['rule']['section']['name']
|
|
153
|
+
if section_name not in section_counters:
|
|
154
|
+
section_counters[section_name] = 0
|
|
155
|
+
section_counters[section_name] += 1
|
|
156
|
+
rule_data['rule']['index_in_section'] = section_counters[section_name]
|
|
157
|
+
|
|
158
|
+
# Create rules_in_sections array
|
|
159
|
+
rules_in_sections = []
|
|
160
|
+
for rule_data in processed_data['data']['policy']['internetFirewall']['policy']['rules']:
|
|
161
|
+
rule_info = rule_data['rule']
|
|
162
|
+
rules_in_sections.append({
|
|
163
|
+
"index_in_section": rule_info['index_in_section'],
|
|
164
|
+
"section_name": rule_info['section']['name'],
|
|
165
|
+
"rule_name": rule_info['name']
|
|
166
|
+
})
|
|
167
|
+
rule_info.pop("index_in_section", None)
|
|
168
|
+
rule_info.pop("index", None)
|
|
169
|
+
# rule_info["enabled"] = True
|
|
170
|
+
|
|
171
|
+
# Add rules_in_sections to the policy structure
|
|
172
|
+
processed_data['data']['policy']['internetFirewall']['policy']['rules_in_sections'] = rules_in_sections
|
|
173
|
+
|
|
174
|
+
# Reformat sections array to have index, section_id and section_name structure
|
|
175
|
+
# Exclude the first section from export
|
|
176
|
+
processed_sections = []
|
|
177
|
+
for index, section_data in enumerate(processed_data['data']['policy']['internetFirewall']['policy']['sections']):
|
|
178
|
+
if index > 0: # Skip the first section (index 0)
|
|
179
|
+
processed_sections.append({
|
|
180
|
+
"section_index": index,
|
|
181
|
+
"section_name": section_data['section']['name']
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
# Add preserved section IDs and section_to_start_after_id
|
|
185
|
+
processed_data['data']['policy']['internetFirewall']['policy']['section_ids'] = section_id_map
|
|
186
|
+
if section_to_start_after_id:
|
|
187
|
+
processed_data['data']['policy']['internetFirewall']['policy']['section_to_start_after_id'] = section_to_start_after_id
|
|
188
|
+
|
|
189
|
+
# Replace the original sections array with the reformatted one
|
|
190
|
+
processed_data['data']['policy']['internetFirewall']['policy']['sections'] = processed_sections
|
|
191
|
+
|
|
192
|
+
# Write the processed data to the new file
|
|
193
|
+
with open(output_file, 'w', encoding='utf-8') as f:
|
|
194
|
+
json.dump(processed_data, f, indent=4, ensure_ascii=False)
|
|
195
|
+
|
|
196
|
+
return [{"success": True, "output_file": output_file, "account_id": account_id}]
|
|
197
|
+
|
|
198
|
+
except Exception as e:
|
|
199
|
+
print(f"ERROR: {str(e)}")
|
|
200
|
+
return [{"success": False, "error": str(e)}]
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def export_wf_rules_to_json(args, configuration):
|
|
204
|
+
"""
|
|
205
|
+
Export WAN Firewall rules to JSON format
|
|
206
|
+
Adapted from scripts/export_wf_rules_to_json.py
|
|
207
|
+
"""
|
|
208
|
+
try:
|
|
209
|
+
# Get account ID from args, configuration, or environment variable
|
|
210
|
+
account_id = None
|
|
211
|
+
if hasattr(args, 'accountID') and args.accountID:
|
|
212
|
+
account_id = args.accountID
|
|
213
|
+
elif hasattr(configuration, 'accountID') and configuration.accountID:
|
|
214
|
+
account_id = configuration.accountID
|
|
215
|
+
else:
|
|
216
|
+
account_id = os.getenv('CATO_ACCOUNT_ID')
|
|
217
|
+
|
|
218
|
+
if not account_id:
|
|
219
|
+
raise ValueError("Account ID is required. Provide it using the -accountID flag or set CATO_ACCOUNT_ID environment variable.")
|
|
220
|
+
|
|
221
|
+
# Set up output file path
|
|
222
|
+
if hasattr(args, 'output_file_path') and args.output_file_path:
|
|
223
|
+
# Use output file path if provided
|
|
224
|
+
output_file = args.output_file_path
|
|
225
|
+
destination_dir = os.path.dirname(output_file)
|
|
226
|
+
if hasattr(args, 'verbose') and args.verbose:
|
|
227
|
+
print(f"Using output file path: {output_file}")
|
|
228
|
+
else:
|
|
229
|
+
# Use default path and filename
|
|
230
|
+
destination_dir = 'config_data'
|
|
231
|
+
json_output_file = f"all_wf_rules_and_sections_{account_id}.json"
|
|
232
|
+
output_file = os.path.join(destination_dir, json_output_file)
|
|
233
|
+
if hasattr(args, 'verbose') and args.verbose:
|
|
234
|
+
print(f"Using default path: {output_file}")
|
|
235
|
+
|
|
236
|
+
# Create destination directory if it doesn't exist
|
|
237
|
+
if destination_dir and not os.path.exists(destination_dir):
|
|
238
|
+
if hasattr(args, 'verbose') and args.verbose:
|
|
239
|
+
print(f"Creating directory: {destination_dir}")
|
|
240
|
+
os.makedirs(destination_dir)
|
|
241
|
+
|
|
242
|
+
# Define the GraphQL query for WAN Firewall
|
|
243
|
+
policy_query = {
|
|
244
|
+
"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 } } } } }",
|
|
245
|
+
"variables": {
|
|
246
|
+
"accountId": account_id
|
|
247
|
+
},
|
|
248
|
+
"operationName": "policy"
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
# Create API client instance with params
|
|
252
|
+
instance = CallApi(ApiClient(configuration))
|
|
253
|
+
params = {
|
|
254
|
+
'v': hasattr(args, 'verbose') and args.verbose, # verbose mode
|
|
255
|
+
'f': 'json', # format
|
|
256
|
+
'p': False, # pretty print
|
|
257
|
+
't': False # test mode
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
try:
|
|
261
|
+
# Call the API directly
|
|
262
|
+
response = instance.call_api(policy_query, params)
|
|
263
|
+
all_wf_rules = response[0] if response else {}
|
|
264
|
+
|
|
265
|
+
# Show raw API response in verbose mode
|
|
266
|
+
if hasattr(args, 'verbose') and args.verbose:
|
|
267
|
+
print("\n" + "=" * 80)
|
|
268
|
+
print("RAW API RESPONSE:")
|
|
269
|
+
print("=" * 80)
|
|
270
|
+
print(json.dumps(all_wf_rules, indent=2))
|
|
271
|
+
print("=" * 80 + "\n")
|
|
272
|
+
|
|
273
|
+
# Check for GraphQL errors first
|
|
274
|
+
if 'errors' in all_wf_rules:
|
|
275
|
+
error_messages = [error.get('message', 'Unknown error') for error in all_wf_rules['errors']]
|
|
276
|
+
raise Exception(f"API returned errors: {', '.join(error_messages)}")
|
|
277
|
+
|
|
278
|
+
if not all_wf_rules or 'data' not in all_wf_rules:
|
|
279
|
+
raise ValueError("Failed to retrieve data from API")
|
|
280
|
+
|
|
281
|
+
except ApiException as e:
|
|
282
|
+
raise Exception(f"API call failed - {e}")
|
|
283
|
+
except Exception as e:
|
|
284
|
+
raise Exception(f"Unexpected error during API call - {e}")
|
|
285
|
+
|
|
286
|
+
# IMPORTANT: Preserve section IDs BEFORE stripping them
|
|
287
|
+
section_id_map = {}
|
|
288
|
+
section_to_start_after_id = None
|
|
289
|
+
sections_with_ids = all_wf_rules['data']['policy']['wanFirewall']['policy']['sections']
|
|
290
|
+
for index, section_data in enumerate(sections_with_ids):
|
|
291
|
+
section_name = section_data['section']['name']
|
|
292
|
+
section_id = section_data['section']['id']
|
|
293
|
+
section_id_map[section_name] = section_id
|
|
294
|
+
|
|
295
|
+
# Processing data to strip id attributes
|
|
296
|
+
processed_data = strip_ids_recursive(all_wf_rules)
|
|
297
|
+
|
|
298
|
+
# Filter out rules with properties[0]=="SYSTEM"
|
|
299
|
+
filtered_rules = []
|
|
300
|
+
for rule_data in processed_data['data']['policy']['wanFirewall']['policy']['rules']:
|
|
301
|
+
rule_properties = rule_data.get('properties', [])
|
|
302
|
+
# Skip rules where the first property is "SYSTEM"
|
|
303
|
+
if rule_properties and rule_properties[0] == "SYSTEM":
|
|
304
|
+
if hasattr(args, 'verbose') and args.verbose:
|
|
305
|
+
print(f"Excluding SYSTEM rule: {rule_data['rule']['name']}")
|
|
306
|
+
else:
|
|
307
|
+
filtered_rules.append(rule_data)
|
|
308
|
+
|
|
309
|
+
processed_data['data']['policy']['wanFirewall']['policy']['rules'] = filtered_rules
|
|
310
|
+
|
|
311
|
+
# Add index_in_section to each rule
|
|
312
|
+
section_counters = {}
|
|
313
|
+
for rule_data in processed_data['data']['policy']['wanFirewall']['policy']['rules']:
|
|
314
|
+
section_name = rule_data['rule']['section']['name']
|
|
315
|
+
if section_name not in section_counters:
|
|
316
|
+
section_counters[section_name] = 0
|
|
317
|
+
section_counters[section_name] += 1
|
|
318
|
+
rule_data['rule']['index_in_section'] = section_counters[section_name]
|
|
319
|
+
|
|
320
|
+
# Create rules_in_sections array
|
|
321
|
+
rules_in_sections = []
|
|
322
|
+
for rule_data in processed_data['data']['policy']['wanFirewall']['policy']['rules']:
|
|
323
|
+
rule_info = rule_data['rule']
|
|
324
|
+
rules_in_sections.append({
|
|
325
|
+
"index_in_section": rule_info['index_in_section'],
|
|
326
|
+
"section_name": rule_info['section']['name'],
|
|
327
|
+
"rule_name": rule_info['name']
|
|
328
|
+
})
|
|
329
|
+
# rule_info.pop("index_in_section", None)
|
|
330
|
+
# rule_info.pop("index", None)
|
|
331
|
+
# rule_info["enabled"] = True
|
|
332
|
+
|
|
333
|
+
# Add rules_in_sections to the policy structure
|
|
334
|
+
processed_data['data']['policy']['wanFirewall']['policy']['rules_in_sections'] = rules_in_sections
|
|
335
|
+
|
|
336
|
+
# Reformat sections array to have index, section_id and section_name structure
|
|
337
|
+
# Exclude the first section from export
|
|
338
|
+
processed_sections = []
|
|
339
|
+
for index, section_data in enumerate(processed_data['data']['policy']['wanFirewall']['policy']['sections']):
|
|
340
|
+
processed_sections.append({
|
|
341
|
+
"section_index": index,
|
|
342
|
+
"section_name": section_data['section']['name']
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
# Add preserved section IDs and section_to_start_after_id
|
|
346
|
+
processed_data['data']['policy']['wanFirewall']['policy']['section_ids'] = section_id_map
|
|
347
|
+
if section_to_start_after_id:
|
|
348
|
+
processed_data['data']['policy']['wanFirewall']['policy']['section_to_start_after_id'] = section_to_start_after_id
|
|
349
|
+
|
|
350
|
+
# Replace the original sections array with the reformatted one
|
|
351
|
+
processed_data['data']['policy']['wanFirewall']['policy']['sections'] = processed_sections
|
|
352
|
+
|
|
353
|
+
# Write the processed data to the new file
|
|
354
|
+
with open(output_file, 'w', encoding='utf-8') as f:
|
|
355
|
+
json.dump(processed_data, f, indent=4, ensure_ascii=False)
|
|
356
|
+
|
|
357
|
+
return [{"success": True, "output_file": output_file, "account_id": account_id}]
|
|
358
|
+
|
|
359
|
+
except Exception as e:
|
|
360
|
+
print(f"ERROR: {str(e)}")
|
|
361
|
+
return [{"success": False, "error": str(e)}]
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import catocli.parsers.custom.import_rules_to_tf.import_rules_to_tf as import_rules_to_tf
|
|
2
|
+
|
|
3
|
+
def import_parse(subparsers):
|
|
4
|
+
"""Create import command parsers"""
|
|
5
|
+
|
|
6
|
+
# Create the main import parser
|
|
7
|
+
import_parser = subparsers.add_parser('import', help='Import data from various sources', usage='catocli import <operation> [options]')
|
|
8
|
+
import_subparsers = import_parser.add_subparsers(description='valid import operations', help='additional help')
|
|
9
|
+
|
|
10
|
+
# Add if_rules_to_tf command
|
|
11
|
+
if_rules_parser = import_subparsers.add_parser(
|
|
12
|
+
'if_rules_to_tf',
|
|
13
|
+
help='Import Internet Firewall rules to Terraform state',
|
|
14
|
+
usage='catocli import if_rules_to_tf <json_file> --module-name <module_name> [options]'
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
if_rules_parser.add_argument('json_file', help='Path to the JSON file containing IFW rules and sections')
|
|
18
|
+
if_rules_parser.add_argument('--module-name', required=True,
|
|
19
|
+
help='Terraform module name to import resources into')
|
|
20
|
+
if_rules_parser.add_argument('-accountID', help='Account ID (required by CLI framework but not used for import)', required=False)
|
|
21
|
+
if_rules_parser.add_argument('--batch-size', type=int, default=10,
|
|
22
|
+
help='Number of imports per batch (default: 10)')
|
|
23
|
+
if_rules_parser.add_argument('--delay', type=int, default=2,
|
|
24
|
+
help='Delay between batches in seconds (default: 2)')
|
|
25
|
+
if_rules_parser.add_argument('--rules-only', action='store_true',
|
|
26
|
+
help='Import only rules, skip sections')
|
|
27
|
+
if_rules_parser.add_argument('--sections-only', action='store_true',
|
|
28
|
+
help='Import only sections, skip rules')
|
|
29
|
+
if_rules_parser.add_argument('-v', '--verbose', action='store_true', help='Verbose output')
|
|
30
|
+
if_rules_parser.add_argument('--auto-approve', action='store_true', help='Skip confirmation prompt and proceed automatically')
|
|
31
|
+
|
|
32
|
+
if_rules_parser.set_defaults(func=import_rules_to_tf.import_if_rules_to_tf)
|
|
33
|
+
|
|
34
|
+
# Add wf_rules_to_tf command
|
|
35
|
+
wf_rules_parser = import_subparsers.add_parser(
|
|
36
|
+
'wf_rules_to_tf',
|
|
37
|
+
help='Import WAN Firewall rules to Terraform state',
|
|
38
|
+
usage='catocli import wf_rules_to_tf <json_file> --module-name <module_name> [options]'
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
wf_rules_parser.add_argument('json_file', help='Path to the JSON file containing WF rules and sections')
|
|
42
|
+
wf_rules_parser.add_argument('--module-name', required=True,
|
|
43
|
+
help='Terraform module name to import resources into')
|
|
44
|
+
wf_rules_parser.add_argument('-accountID', help='Account ID (required by CLI framework but not used for import)', required=False)
|
|
45
|
+
wf_rules_parser.add_argument('--batch-size', type=int, default=10,
|
|
46
|
+
help='Number of imports per batch (default: 10)')
|
|
47
|
+
wf_rules_parser.add_argument('--delay', type=int, default=2,
|
|
48
|
+
help='Delay between batches in seconds (default: 2)')
|
|
49
|
+
wf_rules_parser.add_argument('--rules-only', action='store_true',
|
|
50
|
+
help='Import only rules, skip sections')
|
|
51
|
+
wf_rules_parser.add_argument('--sections-only', action='store_true',
|
|
52
|
+
help='Import only sections, skip rules')
|
|
53
|
+
wf_rules_parser.add_argument('-v', '--verbose', action='store_true', help='Verbose output')
|
|
54
|
+
wf_rules_parser.add_argument('--auto-approve', action='store_true', help='Skip confirmation prompt and proceed automatically')
|
|
55
|
+
|
|
56
|
+
wf_rules_parser.set_defaults(func=import_rules_to_tf.import_wf_rules_to_tf)
|
|
57
|
+
|
|
58
|
+
return import_parser
|