Skip to content

Commit 9bca548

Browse files
author
Shiyue Cheng
authored
Script to disable access to the local status page
This script iterates through a dashboard org's templates and updates the appliances' allowed remote IPs for the web service (on Firewall page) to "blocked"/"None". The same change is also applied to all networks in the org not bound to templates, as well as disabling their local status pages.
1 parent 7fa8124 commit 9bca548

File tree

1 file changed

+282
-0
lines changed

1 file changed

+282
-0
lines changed

disable_status_page.py

+282
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
#!/usr/bin/python3
2+
3+
READ_ME = '''
4+
=== PREREQUISITES ===
5+
Run in Python 3.6+
6+
7+
Install the requests & Meraki Python modules:
8+
pip[3] install --upgrade requests
9+
pip[3] install --upgrade meraki
10+
11+
=== DESCRIPTION ===
12+
This script iterates through a dashboard org's templates and updates the
13+
appliances' allowed remote IPs for the web service (on Firewall page) to
14+
"blocked"/"None". The same change is also applied to all networks in the org
15+
not bound to templates, as well as disabling their local status pages.
16+
17+
=== USAGE ===
18+
python[3] disable_status_page.py -k <api_key> -o <org_id> [-m <mode>]
19+
Mode defaults to "simulate" unless "commit" is specified.
20+
21+
'''
22+
23+
24+
import csv
25+
from datetime import datetime
26+
import getopt
27+
import json
28+
import logging
29+
import requests
30+
import sys
31+
from meraki import meraki
32+
33+
base_url = 'https://api.meraki.com/api/v0/'
34+
35+
logger = logging.getLogger(__name__)
36+
def configure_logging():
37+
logging.basicConfig(
38+
filename='{}_log_{:%Y%m%d_%H%M%S}.txt'.format(sys.argv[0].split('.')[0], datetime.now()),
39+
level=logging.DEBUG,
40+
format='%(asctime)s: %(levelname)7s: [%(name)s]: %(message)s',
41+
datefmt='%Y-%m-%d %H:%M:%S'
42+
)
43+
44+
# Prints READ_ME help message for user to read
45+
def print_help():
46+
lines = READ_ME.split('\n')
47+
for line in lines:
48+
print('# {0}'.format(line))
49+
50+
# Update a network
51+
# https://api.meraki.com/api_docs#update-a-network
52+
def set_network(api_key, net_id, name=None, timeZone=None, tags=None, disableMyMerakiCom=False, display_result=True):
53+
put_url = f'{base_url}/networks/{net_id}'
54+
headers = {'X-Cisco-Meraki-API-Key': api_key,
55+
'Content-Type': 'application/json'}
56+
57+
if type(tags) == list:
58+
tags = ' '.join(tags)
59+
60+
payload = {key: value for (key, value) in locals().items()
61+
if key in ['name', 'timeZone', 'tags', 'disableMyMerakiCom'] and value != None}
62+
63+
result = requests.put(put_url, headers=headers, data=json.dumps(payload))
64+
if display_result:
65+
print(f'Response status code of {result.status_code}, with content: {result.text}')
66+
return result
67+
68+
# List the appliance services and their accessibility rules
69+
# https://api.meraki.com/api_docs#list-the-appliance-services-and-their-accessibility-rules
70+
def get_appliance_services(api_key, net_id, display_result=True):
71+
get_url = f'{base_url}/networks/{net_id}/firewalledServices'
72+
headers = {'X-Cisco-Meraki-API-Key': api_key,
73+
'Content-Type': 'application/json'}
74+
75+
result = requests.get(get_url, headers=headers)
76+
if display_result:
77+
print(f'Response status code of {result.status_code}, with content: {result.text}')
78+
return result
79+
80+
# Return the accessibility settings of the given service ('ICMP', 'web', or 'SNMP')
81+
# https://api.meraki.com/api_docs#return-the-accessibility-settings-of-the-given-service-icmp-web-or-snmp
82+
def get_appliance_service_setting(api_key, net_id, service=None, display_result=True):
83+
get_url = f'{base_url}/networks/{net_id}/firewalledServices/{service}'
84+
headers = {'X-Cisco-Meraki-API-Key': api_key,
85+
'Content-Type': 'application/json'}
86+
87+
if not service or service.lower() not in ('icmp', 'web', 'snmp'):
88+
raise ValueError(f'parameter service must be either "ICMP", "web", or "SNMP", and cannot be {service} ('
89+
'https://api.meraki.com/api_docs#updates-the-accessibility-settings-for-the-given-service-icmp-web-or-snmp)')
90+
91+
result = requests.get(get_url, headers=headers)
92+
if display_result:
93+
print(f'Response status code of {result.status_code}, with content: {result.text}')
94+
return result
95+
96+
# Updates the accessibility settings for the given service ('ICMP', 'web', or 'SNMP')
97+
# https://api.meraki.com/api_docs#updates-the-accessibility-settings-for-the-given-service-icmp-web-or-snmp
98+
def set_appliance_service_setting(api_key, net_id, service=None, access=None, allowedIps=None, display_result=True):
99+
put_url = f'{base_url}/networks/{net_id}/firewalledServices/{service}'
100+
headers = {'X-Cisco-Meraki-API-Key': api_key,
101+
'Content-Type': 'application/json'}
102+
103+
if not service or service.lower() not in ('icmp', 'web', 'snmp'):
104+
raise ValueError(f'parameter service must be either "ICMP", "web", or "SNMP", and cannot be {service} ('
105+
'https://api.meraki.com/api_docs#updates-the-accessibility-settings-for-the-given-service-icmp-web-or-snmp)')
106+
107+
if not access or access.lower() not in ('blocked', 'restricted', 'unrestricted'):
108+
raise ValueError(f'parameter access must be either "blocked", "restricted", or "unrestricted", and cannot be {access} ('
109+
'https://api.meraki.com/api_docs#updates-the-accessibility-settings-for-the-given-service-icmp-web-or-snmp)')
110+
111+
if access.lower() == 'restricted' and (not allowedIps or type(allowedIps) != list):
112+
raise ValueError(f'parameter allowedIps must be a list of whitelisted IP addresses, and cannot be {allowedIps} ('
113+
'https://api.meraki.com/api_docs#updates-the-accessibility-settings-for-the-given-service-icmp-web-or-snmp)')
114+
115+
payload = {key: value for (key, value) in locals().items() if key in ['access', 'allowedIps'] and value != None}
116+
117+
result = requests.put(put_url, headers=headers, data=json.dumps(payload))
118+
if display_result:
119+
print(f'Response status code of {result.status_code}, with content: {result.text}')
120+
return result
121+
122+
123+
def main(argv):
124+
# Set default values for command line arguments
125+
api_key = org_id = arg_mode = None
126+
127+
# Get command line arguments
128+
try:
129+
opts, args = getopt.getopt(argv, 'hk:o:m:')
130+
except getopt.GetoptError:
131+
print_help()
132+
sys.exit(2)
133+
for opt, arg in opts:
134+
if opt == '-h':
135+
print_help()
136+
sys.exit()
137+
elif opt == '-k':
138+
api_key = arg
139+
elif opt == '-o':
140+
org_id = arg
141+
elif opt == '-m':
142+
arg_mode = arg
143+
144+
# Check if all required parameters have been input
145+
if api_key == None or org_id == None:
146+
print_help()
147+
sys.exit(2)
148+
149+
# Assign default mode to "simulate" unless "commit" specified
150+
if arg_mode != 'commit':
151+
arg_mode = 'simulate'
152+
153+
# Get lists of templates and networks in org
154+
templates = meraki.gettemplates(api_key, org_id)
155+
networks = meraki.getnetworklist(api_key, org_id)
156+
unbound_networks = [network for network in networks if 'configTemplateId' not in network]
157+
158+
# Iterate through all templates
159+
logger.info(f'Iterating through {len(templates)} templates:')
160+
for template in templates:
161+
162+
result = get_appliance_service_setting(api_key, template['id'], 'web')
163+
# Does template have MX appliance component?
164+
if result.ok:
165+
web_status = json.loads(result.text)['access']
166+
167+
# Check current config to see if already disabled
168+
if web_status == 'blocked':
169+
logger.info(f'Appliance web service for template {template["name"]} already disabled/blocked')
170+
csv_row = ['Template', template['name'], template['id'], '?', 'blocked']
171+
csv_writer.writerow(csv_row)
172+
else:
173+
174+
# Update configuration
175+
if arg_mode == 'commit':
176+
logger.info(f'Updating template {template["name"]}...')
177+
result = set_appliance_service_setting(api_key, template['id'], 'web', 'blocked')
178+
if result.ok:
179+
logger.info(f'Blocked remote IPs for web service on template {template["name"]}')
180+
web_status = json.loads(result.text)['access']
181+
else:
182+
logger.error(f'Failed to update appliance web service on {template["name"]}')
183+
web_status = '?'
184+
else:
185+
logger.info(f'Simulating update of template {template["name"]}...')
186+
187+
# Write result to CSV output file
188+
csv_row = ['Template', template['name'], template['id'], '?', web_status]
189+
csv_writer.writerow(csv_row)
190+
191+
else:
192+
# Template without appliance component
193+
csv_row = ['Template', template['name'], template['id'], '?', 'N/A']
194+
csv_writer.writerow(csv_row)
195+
196+
197+
# Iterate through all unbound networks (networks not associated with a template)
198+
logger.info(f'Iterating through {len(unbound_networks)} unbound networks:')
199+
for network in unbound_networks:
200+
201+
# For appliance networks, check web firewall service
202+
if network['type'] in ('appliance', 'combined'):
203+
result = get_appliance_service_setting(api_key, network['id'], 'web')
204+
web_status = json.loads(result.text)['access']
205+
else:
206+
web_status = 'N/A'
207+
208+
# If everything already disabled, make note in CSV & continue
209+
local_status_disabled = network['disableMyMerakiCom']
210+
if local_status_disabled and web_status in ('blocked', 'N/A'):
211+
logger.info(f'Status page for network {network["name"]} already disabled/blocked')
212+
csv_row = ['Network', network['name'], network['id'], 'disabled', web_status]
213+
csv_writer.writerow(csv_row)
214+
else:
215+
# Update configuration
216+
if arg_mode == 'commit':
217+
logger.info(f'Updating network {network["name"]}...')
218+
result1 = set_network(api_key, network['id'], disableMyMerakiCom=True)
219+
if result1.ok:
220+
logger.info(f'Disabled local status page for network {network["name"]}')
221+
local_status_disabled = True
222+
else:
223+
logger.error(f'Failed to update local status page on network {network["name"]}')
224+
if network['type'] in ('appliance', 'combined'):
225+
result2 = set_appliance_service_setting(api_key, network['id'], 'web', 'blocked')
226+
if result2.ok:
227+
logger.info(f'Blocked remote IPs for web service on appliance {network["name"]}')
228+
web_status = json.loads(result2.text)['access']
229+
else:
230+
logger.error(f'Failed to update appliance web service on {network["name"]}')
231+
web_status = '?'
232+
else:
233+
logger.info(f'Simulating update of network {network["name"]}...')
234+
235+
# Write result to CSV output file
236+
lsp_status = 'disabled' if local_status_disabled else 'enabled'
237+
csv_row = ['Network', network['name'], network['id'], lsp_status, web_status]
238+
csv_writer.writerow(csv_row)
239+
240+
241+
if __name__ == '__main__':
242+
inputs = sys.argv[1:]
243+
if len(inputs) == 0:
244+
print_help()
245+
sys.exit(2)
246+
247+
# Configure logging to stdout
248+
configure_logging()
249+
# Define a Handler which writes INFO messages or higher to the sys.stderr
250+
console = logging.StreamHandler()
251+
console.setLevel(logging.INFO)
252+
# Set a format which is simpler for console use
253+
formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s')
254+
# Tell the handler to use this format
255+
console.setFormatter(formatter)
256+
# Add the handler to the root logger
257+
logging.getLogger('').addHandler(console)
258+
259+
# Set the CSV output file and write the header row
260+
time_now = f'{datetime.now():%Y%m%d_%H%M%S}'
261+
file_name = f'status_page_results_{time_now}.csv'
262+
output_file = open(file_name, mode='w', newline='\n')
263+
field_names = ['Type', 'Name','ID','Status Page','Appliance Web Service']
264+
csv_writer = csv.writer(output_file, delimiter=',', quotechar='"', quoting=csv.QUOTE_ALL)
265+
csv_writer.writerow(field_names)
266+
logger.info(f'Output of results to file: {file_name}')
267+
268+
# Output to logfile/console starting inputs
269+
key_index = inputs.index('-k')
270+
inputs.pop(key_index+1)
271+
inputs.pop(key_index)
272+
start_time = datetime.now()
273+
logger.info(f'Started script at {start_time}')
274+
logger.info(f'Input parameters: {inputs}')
275+
276+
# Call main function
277+
main(sys.argv[1:])
278+
279+
# Finish output to logfile/console
280+
end_time = datetime.now()
281+
logger.info(f'Ended script at {end_time}')
282+
logger.info(f'Total run time = {end_time - start_time}')

0 commit comments

Comments
 (0)