catocli 2.0.1__py3-none-any.whl → 2.0.2__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 +37 -5
- catocli/__init__.py +1 -1
- catocli/parsers/custom/__init__.py +3 -2
- catocli/parsers/custom/customLib.py +252 -1
- catocli/parsers/custom/export_rules/__init__.py +5 -1
- catocli/parsers/custom/export_rules/export_rules.py +31 -182
- catocli/parsers/custom/export_sites/__init__.py +20 -0
- catocli/parsers/custom/export_sites/export_sites.py +233 -0
- catocli/parsers/custom/import_rules_to_tf/__init__.py +2 -2
- catocli/parsers/custom/import_rules_to_tf/import_rules_to_tf.py +19 -9
- catocli/parsers/mutation_accountManagement/__init__.py +15 -18
- catocli/parsers/mutation_admin/__init__.py +15 -18
- catocli/parsers/mutation_container/__init__.py +5 -6
- catocli/parsers/mutation_hardware/__init__.py +5 -6
- catocli/parsers/mutation_policy/__init__.py +582 -504
- catocli/parsers/mutation_policy_terminalServer/README.md +7 -0
- catocli/parsers/mutation_policy_terminalServer_addRule/README.md +18 -0
- catocli/parsers/mutation_policy_terminalServer_addSection/README.md +18 -0
- catocli/parsers/mutation_policy_terminalServer_createPolicyRevision/README.md +18 -0
- catocli/parsers/mutation_policy_terminalServer_discardPolicyRevision/README.md +18 -0
- catocli/parsers/mutation_policy_terminalServer_moveRule/README.md +18 -0
- catocli/parsers/mutation_policy_terminalServer_moveSection/README.md +18 -0
- catocli/parsers/mutation_policy_terminalServer_publishPolicyRevision/README.md +18 -0
- catocli/parsers/mutation_policy_terminalServer_removeRule/README.md +18 -0
- catocli/parsers/mutation_policy_terminalServer_removeSection/README.md +18 -0
- catocli/parsers/mutation_policy_terminalServer_updatePolicy/README.md +18 -0
- catocli/parsers/mutation_policy_terminalServer_updateRule/README.md +18 -0
- catocli/parsers/mutation_policy_terminalServer_updateSection/README.md +18 -0
- catocli/parsers/mutation_sandbox/__init__.py +10 -12
- catocli/parsers/mutation_site/__init__.py +164 -150
- catocli/parsers/mutation_site_addSocketAddOnCard/README.md +17 -0
- catocli/parsers/mutation_site_removeSocketAddOnCard/README.md +17 -0
- catocli/parsers/mutation_site_startSiteUpgrade/README.md +17 -0
- catocli/parsers/mutation_sites/__init__.py +164 -150
- catocli/parsers/mutation_sites_addSocketAddOnCard/README.md +17 -0
- catocli/parsers/mutation_sites_removeSocketAddOnCard/README.md +17 -0
- catocli/parsers/mutation_sites_startSiteUpgrade/README.md +17 -0
- catocli/parsers/mutation_xdr/__init__.py +15 -18
- catocli/parsers/query_accountBySubdomain/__init__.py +5 -6
- catocli/parsers/query_accountManagement/__init__.py +5 -6
- catocli/parsers/query_accountMetrics/__init__.py +5 -6
- catocli/parsers/query_accountRoles/__init__.py +5 -6
- catocli/parsers/query_accountSnapshot/__init__.py +5 -6
- catocli/parsers/query_admin/__init__.py +5 -6
- catocli/parsers/query_admins/__init__.py +5 -6
- catocli/parsers/query_appStats/__init__.py +5 -6
- catocli/parsers/query_appStatsTimeSeries/__init__.py +5 -6
- catocli/parsers/query_auditFeed/__init__.py +5 -6
- catocli/parsers/query_catalogs/__init__.py +5 -6
- catocli/parsers/query_container/__init__.py +5 -6
- catocli/parsers/query_devices/README.md +2 -1
- catocli/parsers/query_devices/__init__.py +5 -6
- catocli/parsers/query_entityLookup/__init__.py +5 -6
- catocli/parsers/query_events/__init__.py +5 -6
- catocli/parsers/query_eventsFeed/README.md +1 -1
- catocli/parsers/query_eventsFeed/__init__.py +5 -6
- catocli/parsers/query_eventsTimeSeries/__init__.py +5 -6
- catocli/parsers/query_hardware/__init__.py +5 -6
- catocli/parsers/query_hardwareManagement/__init__.py +5 -6
- catocli/parsers/query_licensing/__init__.py +5 -6
- catocli/parsers/query_policy/README.md +2 -1
- catocli/parsers/query_policy/__init__.py +5 -6
- catocli/parsers/query_sandbox/__init__.py +5 -6
- catocli/parsers/query_site/README.md +2 -1
- catocli/parsers/query_site/__init__.py +5 -6
- catocli/parsers/query_siteLocation/__init__.py +3 -7
- catocli/parsers/query_subDomains/__init__.py +5 -6
- catocli/parsers/query_xdr/__init__.py +10 -12
- catocli/parsers/raw/__init__.py +2 -0
- {catocli-2.0.1.dist-info → catocli-2.0.2.dist-info}/METADATA +1 -1
- {catocli-2.0.1.dist-info → catocli-2.0.2.dist-info}/RECORD +104 -65
- graphql_client/api/call_api.py +12 -6
- models/mutation.policy.remotePortFwd.updateRule.json +6 -6
- models/mutation.policy.terminalServer.addRule.json +2403 -0
- models/mutation.policy.terminalServer.addSection.json +1358 -0
- models/mutation.policy.terminalServer.createPolicyRevision.json +1873 -0
- models/mutation.policy.terminalServer.discardPolicyRevision.json +1807 -0
- models/mutation.policy.terminalServer.moveRule.json +1605 -0
- models/mutation.policy.terminalServer.moveSection.json +1259 -0
- models/mutation.policy.terminalServer.publishPolicyRevision.json +1864 -0
- models/mutation.policy.terminalServer.removeRule.json +1253 -0
- models/mutation.policy.terminalServer.removeSection.json +958 -0
- models/mutation.policy.terminalServer.updatePolicy.json +1883 -0
- models/mutation.policy.terminalServer.updateRule.json +2096 -0
- models/mutation.policy.terminalServer.updateSection.json +1111 -0
- models/mutation.site.addSocketAddOnCard.json +1050 -0
- models/mutation.site.removeSocketAddOnCard.json +786 -0
- models/mutation.site.startSiteUpgrade.json +802 -0
- models/mutation.sites.addSocketAddOnCard.json +1050 -0
- models/mutation.sites.removeSocketAddOnCard.json +786 -0
- models/mutation.sites.startSiteUpgrade.json +802 -0
- models/query.devices.json +311 -2
- models/query.events.json +48 -0
- models/query.eventsFeed.json +12 -0
- models/query.eventsTimeSeries.json +36 -0
- models/query.licensing.json +21815 -10093
- models/query.policy.json +1898 -305
- models/query.site.json +225 -0
- models/query.siteLocation.json +97190 -295396
- schema/catolib.py +52 -24
- {catocli-2.0.1.dist-info → catocli-2.0.2.dist-info}/LICENSE +0 -0
- {catocli-2.0.1.dist-info → catocli-2.0.2.dist-info}/WHEEL +0 -0
- {catocli-2.0.1.dist-info → catocli-2.0.2.dist-info}/entry_points.txt +0 -0
- {catocli-2.0.1.dist-info → catocli-2.0.2.dist-info}/top_level.txt +0 -0
|
@@ -3,7 +3,7 @@ import json
|
|
|
3
3
|
import sys
|
|
4
4
|
from graphql_client.api.call_api import ApiClient, CallApi
|
|
5
5
|
from graphql_client.api_client import ApiException
|
|
6
|
-
|
|
6
|
+
from ..customLib import writeDataToFile, makeCall, getAccountID
|
|
7
7
|
|
|
8
8
|
def strip_ids_recursive(data):
|
|
9
9
|
"""Recursively strip id attributes from data structure, but only from objects that contain only 'id' and 'name' keys"""
|
|
@@ -37,40 +37,7 @@ def export_if_rules_to_json(args, configuration):
|
|
|
37
37
|
Adapted from scripts/export_if_rules_to_json.py
|
|
38
38
|
"""
|
|
39
39
|
try:
|
|
40
|
-
|
|
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
|
|
40
|
+
account_id = getAccountID(args, configuration)
|
|
74
41
|
policy_query = {
|
|
75
42
|
"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
43
|
"variables": {
|
|
@@ -78,56 +45,7 @@ def export_if_rules_to_json(args, configuration):
|
|
|
78
45
|
},
|
|
79
46
|
"operationName": "policy"
|
|
80
47
|
}
|
|
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
|
|
48
|
+
all_ifw_rules = makeCall(args, configuration, policy_query)
|
|
131
49
|
|
|
132
50
|
# Processing data to strip id attributes
|
|
133
51
|
processed_data = strip_ids_recursive(all_ifw_rules)
|
|
@@ -173,26 +91,28 @@ def export_if_rules_to_json(args, configuration):
|
|
|
173
91
|
|
|
174
92
|
# Reformat sections array to have index, section_id and section_name structure
|
|
175
93
|
# Exclude the first section from export
|
|
94
|
+
sections_with_ids = all_ifw_rules['data']['policy']['internetFirewall']['policy']['sections']
|
|
176
95
|
processed_sections = []
|
|
177
|
-
for index, section_data in enumerate(
|
|
96
|
+
for index, section_data in enumerate(sections_with_ids):
|
|
97
|
+
# print("sections_with_ids",json.dumps(section_data, indent=2))
|
|
178
98
|
if index > 0: # Skip the first section (index 0)
|
|
179
99
|
processed_sections.append({
|
|
180
100
|
"section_index": index,
|
|
181
|
-
"section_name": section_data['section']['name']
|
|
101
|
+
"section_name": section_data['section']['name'],
|
|
102
|
+
"section_id": section_data['section']['id']
|
|
182
103
|
})
|
|
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
104
|
|
|
189
105
|
# Replace the original sections array with the reformatted one
|
|
190
106
|
processed_data['data']['policy']['internetFirewall']['policy']['sections'] = processed_sections
|
|
191
107
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
108
|
+
output_file = writeDataToFile(
|
|
109
|
+
data=processed_data,
|
|
110
|
+
args=args,
|
|
111
|
+
account_id=account_id,
|
|
112
|
+
default_filename_template="all_ifw_rules_and_sections_{account_id}.json",
|
|
113
|
+
default_directory="config_data"
|
|
114
|
+
)
|
|
115
|
+
|
|
196
116
|
return [{"success": True, "output_file": output_file, "account_id": account_id}]
|
|
197
117
|
|
|
198
118
|
except Exception as e:
|
|
@@ -206,40 +126,8 @@ def export_wf_rules_to_json(args, configuration):
|
|
|
206
126
|
Adapted from scripts/export_wf_rules_to_json.py
|
|
207
127
|
"""
|
|
208
128
|
try:
|
|
209
|
-
|
|
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.")
|
|
129
|
+
account_id = getAccountID(args, configuration)
|
|
220
130
|
|
|
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
131
|
policy_query = {
|
|
244
132
|
"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
133
|
"variables": {
|
|
@@ -247,51 +135,11 @@ def export_wf_rules_to_json(args, configuration):
|
|
|
247
135
|
},
|
|
248
136
|
"operationName": "policy"
|
|
249
137
|
}
|
|
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")
|
|
138
|
+
all_wf_rules = makeCall(args, configuration, policy_query)
|
|
280
139
|
|
|
281
|
-
|
|
282
|
-
raise
|
|
283
|
-
except Exception as e:
|
|
284
|
-
raise Exception(f"Unexpected error during API call - {e}")
|
|
140
|
+
if not all_wf_rules or 'data' not in all_wf_rules:
|
|
141
|
+
raise ValueError("Failed to retrieve data from API")
|
|
285
142
|
|
|
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
143
|
# Processing data to strip id attributes
|
|
296
144
|
processed_data = strip_ids_recursive(all_wf_rules)
|
|
297
145
|
|
|
@@ -335,24 +183,25 @@ def export_wf_rules_to_json(args, configuration):
|
|
|
335
183
|
|
|
336
184
|
# Reformat sections array to have index, section_id and section_name structure
|
|
337
185
|
# Exclude the first section from export
|
|
186
|
+
sections_with_ids = all_wf_rules['data']['policy']['wanFirewall']['policy']['sections']
|
|
338
187
|
processed_sections = []
|
|
339
|
-
for index, section_data in enumerate(
|
|
188
|
+
for index, section_data in enumerate(sections_with_ids):
|
|
340
189
|
processed_sections.append({
|
|
341
190
|
"section_index": index,
|
|
342
|
-
"section_name": section_data['section']['name']
|
|
191
|
+
"section_name": section_data['section']['name'],
|
|
192
|
+
"section_id": section_data['section']['id']
|
|
343
193
|
})
|
|
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
194
|
|
|
350
195
|
# Replace the original sections array with the reformatted one
|
|
351
196
|
processed_data['data']['policy']['wanFirewall']['policy']['sections'] = processed_sections
|
|
352
197
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
198
|
+
output_file = writeDataToFile(
|
|
199
|
+
data=processed_data,
|
|
200
|
+
args=args,
|
|
201
|
+
account_id=account_id,
|
|
202
|
+
default_filename_template="all_wf_rules_and_sections_{account_id}.json",
|
|
203
|
+
default_directory="config_data"
|
|
204
|
+
)
|
|
356
205
|
|
|
357
206
|
return [{"success": True, "output_file": output_file, "account_id": account_id}]
|
|
358
207
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import catocli.parsers.custom.export_sites.export_sites as export_sites
|
|
2
|
+
|
|
3
|
+
def export_sites_parse(subparsers):
|
|
4
|
+
"""Create export_sites command parsers"""
|
|
5
|
+
|
|
6
|
+
# Create the socket_sites parser (direct command, no subparsers)
|
|
7
|
+
socket_sites_parser = subparsers.add_parser(
|
|
8
|
+
'socket_sites',
|
|
9
|
+
help='Export consolidated site and socket data to JSON format',
|
|
10
|
+
usage='catocli export socket_sites [-accountID <account_id>] [options]'
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
socket_sites_parser.add_argument('-accountID', help='Account ID to export data from (uses CATO_ACCOUNT_ID environment variable if not specified)', required=False)
|
|
14
|
+
socket_sites_parser.add_argument('-siteIDs', help='Comma-separated list of site IDs to export (e.g., "132606,132964,133511")', required=False)
|
|
15
|
+
socket_sites_parser.add_argument('--output-file-path', help='Full path including filename and extension for output file. If not specified, uses default: config_data/socket_site_data_{account_id}.json')
|
|
16
|
+
socket_sites_parser.add_argument('-v', '--verbose', action='store_true', help='Verbose output')
|
|
17
|
+
|
|
18
|
+
socket_sites_parser.set_defaults(func=export_sites.export_socket_site_to_json)
|
|
19
|
+
|
|
20
|
+
return socket_sites_parser
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
from graphql_client.api.call_api import ApiClient, CallApi
|
|
4
|
+
from graphql_client.api_client import ApiException
|
|
5
|
+
from ..customLib import writeDataToFile, makeCall, getAccountID
|
|
6
|
+
|
|
7
|
+
def export_socket_site_to_json(args, configuration):
|
|
8
|
+
"""
|
|
9
|
+
Export consolidated site and socket data to JSON format
|
|
10
|
+
"""
|
|
11
|
+
processed_data = {'sites':{}}
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
account_id = getAccountID(args, configuration)
|
|
15
|
+
# Get account snapshot with siteIDs if provided
|
|
16
|
+
# Get siteIDs from args if provided (comma-separated string)
|
|
17
|
+
site_ids = []
|
|
18
|
+
if hasattr(args, 'siteIDs') and args.siteIDs:
|
|
19
|
+
# Parse comma-separated string into list, removing whitespace
|
|
20
|
+
site_ids = [site_id.strip() for site_id in args.siteIDs.split(',') if site_id.strip()]
|
|
21
|
+
if hasattr(args, 'verbose') and args.verbose:
|
|
22
|
+
print(f"Filtering snapshot for site IDs: {site_ids}")
|
|
23
|
+
|
|
24
|
+
###############################################################
|
|
25
|
+
## Call APIs to retrieve sites, interface and network ranges ##
|
|
26
|
+
###############################################################
|
|
27
|
+
snapshot_sites = getAccountSnapshot(args, configuration, account_id, site_ids)
|
|
28
|
+
entity_network_interfaces = getEntityLookup(args, configuration, account_id, "networkInterface")
|
|
29
|
+
entity_network_ranges = getEntityLookup(args, configuration, account_id, "siteRange")
|
|
30
|
+
entity_sites = getEntityLookup(args, configuration, account_id, "site")
|
|
31
|
+
|
|
32
|
+
##################################################################
|
|
33
|
+
## Create processed_data object indexed by siteId with location ##
|
|
34
|
+
##################################################################
|
|
35
|
+
for site_data in snapshot_sites:
|
|
36
|
+
cur_site = {
|
|
37
|
+
'wan_interfaces': {},
|
|
38
|
+
'lan_interfaces': {},
|
|
39
|
+
}
|
|
40
|
+
site_id = site_data.get('id')
|
|
41
|
+
cur_site['id'] = site_id
|
|
42
|
+
cur_site['name'] = site_data.get('infoSiteSnapshot', {}).get('name')
|
|
43
|
+
cur_site['description'] = site_data.get('infoSiteSnapshot', {}).get('description')
|
|
44
|
+
cur_site['connectionType'] = site_data.get('infoSiteSnapshot', {}).get('connType')
|
|
45
|
+
cur_site['type'] = site_data.get('infoSiteSnapshot', {}).get('type')
|
|
46
|
+
cur_site = populateSiteLocationData(args, site_data, cur_site)
|
|
47
|
+
|
|
48
|
+
site_interfaces = site_data.get('infoSiteSnapshot', {}).get('interfaces', [])
|
|
49
|
+
for wan_ni in site_interfaces:
|
|
50
|
+
cur_wan_interface = {}
|
|
51
|
+
id = wan_ni.get('wanRoleInterfaceInfo', "")
|
|
52
|
+
if id[0:3] == "wan":
|
|
53
|
+
cur_wan_interface['id'] = id
|
|
54
|
+
cur_wan_interface['name'] = wan_ni.get('name', "")
|
|
55
|
+
cur_wan_interface['upstreamBandwidth'] = wan_ni.get('upstreamBandwidth', 0)
|
|
56
|
+
cur_wan_interface['downstreamBandwidth'] = wan_ni.get('downstreamBandwidth', 0)
|
|
57
|
+
cur_wan_interface['destType'] = wan_ni.get('destType', "")
|
|
58
|
+
cur_site['wan_interfaces'][id] = cur_wan_interface
|
|
59
|
+
|
|
60
|
+
if site_id:
|
|
61
|
+
processed_data['sites'][site_id] = cur_site
|
|
62
|
+
|
|
63
|
+
##################################################################################
|
|
64
|
+
## Process entity lookup LAN network interfaces adding to site object by site_id##
|
|
65
|
+
##################################################################################
|
|
66
|
+
for lan_ni in entity_network_interfaces:
|
|
67
|
+
cur_lan_interface = {
|
|
68
|
+
'network_ranges': {},
|
|
69
|
+
}
|
|
70
|
+
site_id = lan_ni.get("helperFields","").get('siteId', "")
|
|
71
|
+
id = lan_ni.get('entity', "").get('id', "")
|
|
72
|
+
interfaceName = lan_ni.get('entity', "").get('interfaceName', "")
|
|
73
|
+
cur_lan_interface['id'] = id
|
|
74
|
+
cur_lan_interface['name'] = interfaceName
|
|
75
|
+
cur_lan_interface['index'] = lan_ni.get("helperFields","").get('interfaceId', "")
|
|
76
|
+
cur_lan_interface['upstreamBandwidth'] = lan_ni.get('upstreamBandwidth', 0)
|
|
77
|
+
cur_lan_interface['downstreamBandwidth'] = lan_ni.get('downstreamBandwidth', 0)
|
|
78
|
+
cur_lan_interface['destType'] = lan_ni.get('destType', "")
|
|
79
|
+
processed_data['sites'][site_id]['lan_interfaces'][interfaceName] = cur_lan_interface
|
|
80
|
+
|
|
81
|
+
#############################################################################
|
|
82
|
+
## Process entity lookup network ranges populating by network interface id ##
|
|
83
|
+
#############################################################################
|
|
84
|
+
for range in entity_network_ranges:
|
|
85
|
+
cur_range = {}
|
|
86
|
+
site_id = lan_ni.get("helperFields","").get('siteId', "")
|
|
87
|
+
id = lan_ni.get('entity', "").get('id', "")
|
|
88
|
+
interface_name = lan_ni.get('entity', "").get('interfaceName', "")
|
|
89
|
+
cur_lan_interface['id'] = id
|
|
90
|
+
cur_lan_interface['subnet'] = lan_ni.get("helperFields","").get('subnet', "")
|
|
91
|
+
cur_lan_interface['vlanTag'] = lan_ni.get("helperFields","").get('vlanTag', "")
|
|
92
|
+
cur_lan_interface['microsegmentation'] = lan_ni.get("helperFields","").get('microsegmentation', "")
|
|
93
|
+
|
|
94
|
+
processed_data['sites'][site_id]['lan_interfaces'][interface_name] = cur_range
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# Write the processed data to file using the general-purpose function
|
|
98
|
+
output_file = writeDataToFile(
|
|
99
|
+
data=processed_data,
|
|
100
|
+
args=args,
|
|
101
|
+
account_id=account_id,
|
|
102
|
+
default_filename_template="socket_sites_{account_id}.json",
|
|
103
|
+
default_directory="config_data"
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
return [{"success": True, "output_file": output_file, "account_id": account_id}]
|
|
107
|
+
|
|
108
|
+
except Exception as e:
|
|
109
|
+
print(f"ERROR: {str(e)}")
|
|
110
|
+
return [{"success": False, "error": str(e)}]
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
##########################################################################
|
|
114
|
+
########################### Helper functions #############################
|
|
115
|
+
##########################################################################
|
|
116
|
+
|
|
117
|
+
def populateSiteLocationData(args, site_data, cur_site):
|
|
118
|
+
# Load site location data for timezone and state code lookups
|
|
119
|
+
site_location_data = {}
|
|
120
|
+
try:
|
|
121
|
+
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
122
|
+
models_dir = os.path.join(script_dir, '..', '..', '..', '..', 'models')
|
|
123
|
+
location_file = os.path.join(models_dir, 'query.siteLocation.json')
|
|
124
|
+
|
|
125
|
+
if os.path.exists(location_file):
|
|
126
|
+
with open(location_file, 'r', encoding='utf-8') as f:
|
|
127
|
+
site_location_data = json.load(f)
|
|
128
|
+
if hasattr(args, 'verbose') and args.verbose:
|
|
129
|
+
print(f"Loaded {len(site_location_data)} location entries from {location_file}")
|
|
130
|
+
else:
|
|
131
|
+
if hasattr(args, 'verbose') and args.verbose:
|
|
132
|
+
print(f"Warning: Site location file not found at {location_file}")
|
|
133
|
+
except Exception as e:
|
|
134
|
+
if hasattr(args, 'verbose') and args.verbose:
|
|
135
|
+
print(f"Warning: Could not load site location data: {e}")
|
|
136
|
+
|
|
137
|
+
## siteLocation attributes
|
|
138
|
+
cur_site['address'] = site_data.get('infoSiteSnapshot', {}).get('address')
|
|
139
|
+
cur_site['city'] = site_data.get('infoSiteSnapshot', {}).get('cityName')
|
|
140
|
+
cur_site['stateName'] = site_data.get('infoSiteSnapshot', {}).get('countryStateName')
|
|
141
|
+
cur_site['countryCode'] = site_data.get('infoSiteSnapshot', {}).get('countryCode')
|
|
142
|
+
cur_site['countryName'] = site_data.get('infoSiteSnapshot', {}).get('countryName')
|
|
143
|
+
|
|
144
|
+
# Look up timezone and state code from location data
|
|
145
|
+
country_name = cur_site['countryName']
|
|
146
|
+
state_name = cur_site['stateName']
|
|
147
|
+
city = cur_site['city']
|
|
148
|
+
|
|
149
|
+
# Create lookup key based on available data
|
|
150
|
+
if state_name:
|
|
151
|
+
lookup_key = f"{country_name}___{state_name}___{city}"
|
|
152
|
+
else:
|
|
153
|
+
lookup_key = f"{country_name}___{city}"
|
|
154
|
+
|
|
155
|
+
# Debug output for lookup
|
|
156
|
+
if hasattr(args, 'verbose') and args.verbose:
|
|
157
|
+
print(f"Site {cur_site['name']}: Looking up '{lookup_key}'")
|
|
158
|
+
|
|
159
|
+
# Look up location details
|
|
160
|
+
location_data = site_location_data.get(lookup_key, {})
|
|
161
|
+
|
|
162
|
+
if hasattr(args, 'verbose') and args.verbose:
|
|
163
|
+
if location_data:
|
|
164
|
+
print(f" Found location data: {location_data}")
|
|
165
|
+
else:
|
|
166
|
+
print(f" No location data found for key: {lookup_key}")
|
|
167
|
+
# Try to find similar keys for debugging
|
|
168
|
+
similar_keys = [k for k in site_location_data.keys() if country_name in k and (not city or city in k)][:5]
|
|
169
|
+
if similar_keys:
|
|
170
|
+
print(f" Similar keys found: {similar_keys}")
|
|
171
|
+
|
|
172
|
+
cur_site['stateCode'] = location_data.get('stateCode', None)
|
|
173
|
+
|
|
174
|
+
# Get timezone - always use the 0 element in the timezones array
|
|
175
|
+
timezones = location_data.get('timezone', [])
|
|
176
|
+
cur_site['timezone'] = timezones[0] if timezones else None
|
|
177
|
+
return cur_site
|
|
178
|
+
|
|
179
|
+
def getEntityLookup(args, configuration, account_id, entity_type):
|
|
180
|
+
"""
|
|
181
|
+
Helper function to get entity lookup data for a specific entity type
|
|
182
|
+
"""
|
|
183
|
+
#################################
|
|
184
|
+
## Get entity lookup for sites ##
|
|
185
|
+
#################################
|
|
186
|
+
entity_query = {
|
|
187
|
+
"query": "query entityLookup ( $accountID:ID! $type:EntityType! $sortInput:[SortInput] $lookupFilterInput:[LookupFilterInput] ) { entityLookup ( accountID:$accountID type:$type sort:$sortInput filters:$lookupFilterInput ) { items { entity { id name type } description helperFields } total } }",
|
|
188
|
+
"variables": {
|
|
189
|
+
"accountID": account_id,
|
|
190
|
+
"type": entity_type
|
|
191
|
+
},
|
|
192
|
+
"operationName": "entityLookup"
|
|
193
|
+
}
|
|
194
|
+
response = makeCall(args, configuration, entity_query)
|
|
195
|
+
|
|
196
|
+
# Check for GraphQL errors in snapshot response
|
|
197
|
+
if 'errors' in response:
|
|
198
|
+
error_messages = [error.get('message', 'Unknown error') for error in response['errors']]
|
|
199
|
+
raise Exception(f"Snapshot API returned errors: {', '.join(error_messages)}")
|
|
200
|
+
|
|
201
|
+
if not response or 'data' not in response or 'entityLookup' not in response['data']:
|
|
202
|
+
raise ValueError("Failed to retrieve snapshot data from API")
|
|
203
|
+
|
|
204
|
+
items = response['data']['entityLookup']['items']
|
|
205
|
+
if items is None:
|
|
206
|
+
items = []
|
|
207
|
+
if hasattr(args, 'verbose') and args.verbose:
|
|
208
|
+
print("No items found in entity lookup - "+ entity_type)
|
|
209
|
+
return items
|
|
210
|
+
|
|
211
|
+
def getAccountSnapshot(args, configuration, account_id, site_ids=None):
|
|
212
|
+
snapshot_query = {
|
|
213
|
+
"query": "query accountSnapshot ( $siteIDs:[ID!] $accountID:ID ) { accountSnapshot ( accountID:$accountID ) { id sites ( siteIDs:$siteIDs ) { id protoId connectivityStatusSiteSnapshot: connectivityStatus haStatusSiteSnapshot: haStatus { readiness wanConnectivity keepalive socketVersion } operationalStatusSiteSnapshot: operationalStatus lastConnected connectedSince popName devices { id name identifier connected haRole interfaces { connected id name physicalPort naturalOrder popName previousPopID previousPopName tunnelConnectionReason tunnelUptime tunnelRemoteIP tunnelRemoteIPInfoInterfaceSnapshot: tunnelRemoteIPInfo { ip countryCode countryName city state provider latitude longitude } type infoInterfaceSnapshot: info { id name upstreamBandwidth downstreamBandwidth upstreamBandwidthMbpsPrecision downstreamBandwidthMbpsPrecision destType wanRole } cellularInterfaceInfoInterfaceSnapshot: cellularInterfaceInfo { networkType simSlotId modemStatus isModemConnected iccid imei operatorName isModemSuspended apn apnSelectionMethod signalStrength isRoamingAllowed simNumber disconnectionReason isSimSlot1Detected isSimSlot2Detected } } lastConnected lastDuration connectedSince lastPopID lastPopName recentConnections { duration interfaceName deviceName lastConnected popName remoteIP remoteIPInfoRecentConnection: remoteIPInfo { ip countryCode countryName city state provider latitude longitude } } type deviceUptime socketInfo { id serial isPrimary platformSocketInfo: platform version versionUpdateTime } interfacesLinkState { id up mediaIn linkSpeed duplex hasAddress hasInternet hasTunnel } osType osVersion version versionNumber releaseGroup mfaExpirationTime mfaCreationTime internalIP } infoSiteSnapshot: info { name type description countryCode region countryName countryStateName cityName address isHA connType creationTime interfaces { id name upstreamBandwidth downstreamBandwidth upstreamBandwidthMbpsPrecision downstreamBandwidthMbpsPrecision destType wanRoleInterfaceInfo: wanRole } sockets { id serial isPrimary platformSocketInfo: platform version versionUpdateTime } ipsec { isPrimary catoIP remoteIP ikeVersion } } hostCount altWanStatus } users { id connectivityStatusUserSnapshot: connectivityStatus operationalStatusUserSnapshot: operationalStatus name deviceName uptime lastConnected version versionNumber popID popName remoteIP remoteIPInfoUserSnapshot: remoteIPInfo { ip countryCode countryName city state provider latitude longitude } internalIP osType osVersion devices { id name identifier connected haRole interfaces { connected id name physicalPort naturalOrder popName previousPopID previousPopName tunnelConnectionReason tunnelUptime tunnelRemoteIP tunnelRemoteIPInfoInterfaceSnapshot: tunnelRemoteIPInfo { ip countryCode countryName city state provider latitude longitude } type infoInterfaceSnapshot: info { id name upstreamBandwidth downstreamBandwidth upstreamBandwidthMbpsPrecision downstreamBandwidthMbpsPrecision destType wanRole } cellularInterfaceInfoInterfaceSnapshot: cellularInterfaceInfo { networkType simSlotId modemStatus isModemConnected iccid imei operatorName isModemSuspended apn apnSelectionMethod signalStrength isRoamingAllowed simNumber disconnectionReason isSimSlot1Detected isSimSlot2Detected } } lastConnected lastDuration connectedSince lastPopID lastPopName recentConnections { duration interfaceName deviceName lastConnected popName remoteIP remoteIPInfoRecentConnection: remoteIPInfo { ip countryCode countryName city state provider latitude longitude } } type deviceUptime socketInfo { id serial isPrimary platformSocketInfo: platform version versionUpdateTime } interfacesLinkState { id up mediaIn linkSpeed duplex hasAddress hasInternet hasTunnel } osType osVersion version versionNumber releaseGroup mfaExpirationTime mfaCreationTime internalIP } connectedInOffice infoUserSnapshot: info { name status email creationTime phoneNumber origin authMethod } recentConnections { duration interfaceName deviceName lastConnected popName remoteIP remoteIPInfo { ip countryCode countryName city state provider latitude longitude } } } timestamp } }",
|
|
214
|
+
"variables": {
|
|
215
|
+
"accountID": account_id,
|
|
216
|
+
"siteIDs": site_ids
|
|
217
|
+
},
|
|
218
|
+
"operationName": "accountSnapshot"
|
|
219
|
+
}
|
|
220
|
+
response = makeCall(args, configuration, snapshot_query)
|
|
221
|
+
|
|
222
|
+
# Check for GraphQL errors in snapshot response
|
|
223
|
+
if 'errors' in response:
|
|
224
|
+
error_messages = [error.get('message', 'Unknown error') for error in response['errors']]
|
|
225
|
+
raise Exception(f"Snapshot API returned errors: {', '.join(error_messages)}")
|
|
226
|
+
|
|
227
|
+
if not response or 'data' not in response or 'accountSnapshot' not in response['data']:
|
|
228
|
+
raise ValueError("Failed to retrieve snapshot data from API")
|
|
229
|
+
|
|
230
|
+
if not response or 'sites' not in response['data']['accountSnapshot'] or response['data']['accountSnapshot']['sites'] is None:
|
|
231
|
+
raise ValueError("No sites found in account snapshot data from API")
|
|
232
|
+
|
|
233
|
+
return response
|
|
@@ -11,7 +11,7 @@ def import_parse(subparsers):
|
|
|
11
11
|
if_rules_parser = import_subparsers.add_parser(
|
|
12
12
|
'if_rules_to_tf',
|
|
13
13
|
help='Import Internet Firewall rules to Terraform state',
|
|
14
|
-
usage='catocli import if_rules_to_tf <json_file> --module-name <module_name> [options]'
|
|
14
|
+
usage='catocli import if_rules_to_tf <json_file> --module-name <module_name> [options]\n\nexample: catocli import if_rules_to_tf config_data/all_wf_rules_and_sections.json --module-name module.if_rules'
|
|
15
15
|
)
|
|
16
16
|
|
|
17
17
|
if_rules_parser.add_argument('json_file', help='Path to the JSON file containing IFW rules and sections')
|
|
@@ -35,7 +35,7 @@ def import_parse(subparsers):
|
|
|
35
35
|
wf_rules_parser = import_subparsers.add_parser(
|
|
36
36
|
'wf_rules_to_tf',
|
|
37
37
|
help='Import WAN Firewall rules to Terraform state',
|
|
38
|
-
usage='catocli import wf_rules_to_tf <json_file> --module-name <module_name> [options]'
|
|
38
|
+
usage='catocli import wf_rules_to_tf <json_file> --module-name <module_name> [options]\n\nexample: catocli import wf_rules_to_tf config_data/all_wf_rules_and_sections.json --module-name module.wf_rules'
|
|
39
39
|
)
|
|
40
40
|
|
|
41
41
|
wf_rules_parser.add_argument('json_file', help='Path to the JSON file containing WF rules and sections')
|
|
@@ -60,14 +60,13 @@ def extract_rules_and_sections(policy_data):
|
|
|
60
60
|
})
|
|
61
61
|
|
|
62
62
|
# Extract sections
|
|
63
|
-
section_ids = policy_data.get('section_ids', {})
|
|
64
63
|
for section in policy_data.get('sections', []):
|
|
65
64
|
if section.get('section_name'):
|
|
66
65
|
sections.append({
|
|
67
66
|
'section_name': section['section_name'],
|
|
68
67
|
'section_index': section.get('section_index', 0),
|
|
69
|
-
'section_id':
|
|
70
|
-
})
|
|
68
|
+
'section_id': section.get('section_id', '')
|
|
69
|
+
})
|
|
71
70
|
return rules, sections
|
|
72
71
|
|
|
73
72
|
|
|
@@ -123,8 +122,8 @@ def find_rule_index(rules, rule_name):
|
|
|
123
122
|
return None
|
|
124
123
|
|
|
125
124
|
|
|
126
|
-
def import_sections(sections, module_name,
|
|
127
|
-
|
|
125
|
+
def import_sections(sections, module_name, resource_type,
|
|
126
|
+
resource_name="sections", verbose=False):
|
|
128
127
|
"""Import all sections"""
|
|
129
128
|
print("\nStarting section imports...")
|
|
130
129
|
total_sections = len(sections)
|
|
@@ -135,7 +134,7 @@ def import_sections(sections, module_name, verbose=False,
|
|
|
135
134
|
section_id = section['section_id']
|
|
136
135
|
section_name = section['section_name']
|
|
137
136
|
section_index = section['section_index']
|
|
138
|
-
resource_address = f'{module_name}.{resource_type}.{resource_name}["{
|
|
137
|
+
resource_address = f'{module_name}.{resource_type}.{resource_name}["{section_name}"]'
|
|
139
138
|
print(f"\n[{i+1}/{total_sections}] Section: {section_name} (index: {section_index})")
|
|
140
139
|
|
|
141
140
|
# For sections, we use the section name as the ID since that's how Cato identifies them
|
|
@@ -376,7 +375,7 @@ def import_if_rules_to_tf(args, configuration):
|
|
|
376
375
|
|
|
377
376
|
# Import sections first (if not skipped)
|
|
378
377
|
if not args.rules_only and sections:
|
|
379
|
-
successful, failed = import_sections(sections, module_name=args.module_name, verbose=args.verbose)
|
|
378
|
+
successful, failed = import_sections(sections, module_name=args.module_name, resource_type="cato_if_section", verbose=args.verbose)
|
|
380
379
|
total_successful += successful
|
|
381
380
|
total_failed += failed
|
|
382
381
|
|
|
@@ -439,7 +438,10 @@ def import_wf_sections(sections, module_name, verbose=False,
|
|
|
439
438
|
section_id = section['section_id']
|
|
440
439
|
section_name = section['section_name']
|
|
441
440
|
section_index = section['section_index']
|
|
442
|
-
|
|
441
|
+
# Add module. prefix if not present
|
|
442
|
+
if not module_name.startswith('module.'):
|
|
443
|
+
module_name = f'module.{module_name}'
|
|
444
|
+
resource_address = f'{module_name}.{resource_type}.{resource_name}["{section_name}"]'
|
|
443
445
|
print(f"\n[{i+1}/{total_sections}] Section: {section_name} (index: {section_index})")
|
|
444
446
|
|
|
445
447
|
# For sections, we use the section name as the ID since that's how Cato identifies them
|
|
@@ -469,6 +471,10 @@ def import_wf_rules(rules, module_name, verbose=False,
|
|
|
469
471
|
rule_index = find_rule_index(rules, rule_name)
|
|
470
472
|
terraform_key = sanitize_name_for_terraform(rule_name)
|
|
471
473
|
|
|
474
|
+
# Add module. prefix if not present
|
|
475
|
+
if not module_name.startswith('module.'):
|
|
476
|
+
module_name = f'module.{module_name}'
|
|
477
|
+
|
|
472
478
|
# Use array index syntax instead of rule ID
|
|
473
479
|
resource_address = f'{module_name}.{resource_type}.{resource_name}["{str(rule_name)}"]'
|
|
474
480
|
print(f"\n[{i+1}/{total_rules}] Rule: {rule_name} (index: {rule_index})")
|
|
@@ -519,8 +525,12 @@ def import_wf_rules_to_tf(args, configuration):
|
|
|
519
525
|
print(" No rules or sections found. Exiting.")
|
|
520
526
|
return [{"success": False, "error": "No rules or sections found"}]
|
|
521
527
|
|
|
528
|
+
# Add module. prefix if not present
|
|
529
|
+
module_name = args.module_name
|
|
530
|
+
if not module_name.startswith('module.'):
|
|
531
|
+
module_name = f'module.{module_name}'
|
|
522
532
|
# Validate Terraform environment before proceeding
|
|
523
|
-
validate_terraform_environment(
|
|
533
|
+
validate_terraform_environment(module_name, verbose=args.verbose)
|
|
524
534
|
|
|
525
535
|
# Ask for confirmation (unless auto-approved)
|
|
526
536
|
if not args.rules_only and not args.sections_only:
|