Skip to content

Commit a310d9d

Browse files
author
Shiyue Cheng
committed
Includes new endpoints for March release 0.9
1 parent 600ef95 commit a310d9d

94 files changed

Lines changed: 8691 additions & 24 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

examples/aio_ips2firewall.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import csv
2+
from datetime import datetime, timedelta
3+
import os
4+
import asyncio
5+
import argparse
6+
import ipaddress
7+
from typing import Dict,List
8+
import sys
9+
10+
import meraki.aio
11+
12+
# Either input your API key below, or set an environment variable
13+
# for example, in Terminal on macOS: export MERAKI_DASHBOARD_API_KEY=66839003d2861bc302b292eb66d3b247709f2d0d
14+
api_key = ""
15+
16+
def removeSmallAmounts( ip_counts:Dict[str,int], filter:int):
17+
ret = ip_counts.copy()
18+
for k,v in ip_counts.items():
19+
if v < filter:
20+
ret.pop(k)
21+
22+
return ret
23+
24+
async def analyzeOrganization(aiomeraki: meraki.aio.AsyncDashboardAPI, orgId:str, days:int) -> Dict[str,int]:
25+
ret = {}
26+
timespan = days * 24 * 60 * 60
27+
events = await aiomeraki.security_events.getOrganizationSecurityEvents(orgId, timespan=timespan,total_pages=-1)
28+
for e in events:
29+
ip, port = e["srcIp"].rsplit(":",1)
30+
ip = ip.strip("[]") # remove brackets in case of ipv6
31+
if not ipaddress.ip_address(ip).is_private: # dont block private ip addresses on the public ip of the firewall
32+
ret[ip] = ret.get(ip, 0) + 1
33+
34+
return ret
35+
36+
async def updateFirewallrules(aiomeraki: meraki.aio.AsyncDashboardAPI, networkId:str, ip_list:List[str]):
37+
rules = await aiomeraki.mx_l7_firewall.getNetworkL7FirewallRules(networkId)
38+
39+
rules=rules["rules"]
40+
#get the currently blocked ip ranges
41+
current_blocks = [x["value"] for x in rules if x["type"] == "ipRange"]
42+
43+
new_blocks = current_blocks + list(set(ip_list)-set(current_blocks))
44+
new_blocks = sorted(new_blocks)
45+
46+
#generate new rules based on the list of total ip ranges to block
47+
rules_to_add = [{"policy":"deny", "type":"ipRange", "value":x} for x in new_blocks]
48+
49+
#remove all currently blocked ip ranges
50+
rules = [x for x in rules if x["type"] != "ipRange"]
51+
52+
rules = rules + rules_to_add
53+
54+
await aiomeraki.mx_l7_firewall.updateNetworkL7FirewallRules(networkId,rules=rules)
55+
56+
async def main():
57+
58+
parser = argparse.ArgumentParser(description='Block IP Addresses based on security events')
59+
parser.add_argument('-o','--organization', type=str, nargs='+', dest="organizations", required=True,
60+
help='the name/id of the organization(s) you want to analyze/secure')
61+
parser.add_argument("-f",'--filter', dest='filter', type=int, default=5,
62+
help='how often must an attack be listed before it gets blocked')
63+
parser.add_argument("-s",'--save', dest='save', action='store_true',
64+
help='write the blocklist to all networks in the organization.')
65+
parser.add_argument("-d",'--days', dest='days', default=31, type=int,
66+
help='How many days should be analyzed.')
67+
68+
if len(sys.argv) < 3:
69+
parser.print_help()
70+
return
71+
72+
try:
73+
args = parser.parse_args()
74+
if args.days >= 365:
75+
print("days must be < 365")
76+
parser.print_help()
77+
return
78+
except SystemExit:
79+
return
80+
except:
81+
print("could not parse arguments")
82+
parser.print_help()
83+
return
84+
85+
# Instantiate a Meraki dashboard API session
86+
# NOTE: you have to use "async with" so that the session will be closed correctly at the end of the usage
87+
async with meraki.aio.AsyncDashboardAPI(
88+
api_key,
89+
base_url="https://api.meraki.com/api/v0",
90+
log_file_prefix=__file__[:-3],
91+
print_console=False,
92+
) as aiomeraki:
93+
# Get list of organizations to which API key has access
94+
organizations = await aiomeraki.organizations.getOrganizations()
95+
for x in organizations:
96+
if x["id"] in args.organizations or x["name"] in args.organizations:
97+
print(f"Analyzing organization {x['name']}")
98+
result = await analyzeOrganization(aiomeraki, x["id"], args.days)
99+
result = removeSmallAmounts(result, args.filter)
100+
sum = 0
101+
102+
for k,v in result.items():
103+
print(f"{k} attacked {v} times.")
104+
sum = sum + v
105+
print(f"Total attacks: {sum} from {len(result)} different IP adresses")
106+
107+
#apply the found ip ranges to the firewall
108+
if args.save:
109+
for n in await aiomeraki.networks.getOrganizationNetworks(x["id"]):
110+
print(f"Updating Network {n['name']}")
111+
await updateFirewallrules(aiomeraki,n["id"], result.keys())
112+
113+
print("Script complete!")
114+
115+
116+
if __name__ == "__main__":
117+
loop = asyncio.get_event_loop()
118+
loop.run_until_complete(main())
119+

examples/aio_org_wide_clients.py

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import csv
2+
from datetime import datetime
3+
import os
4+
import asyncio
5+
6+
import meraki.aio
7+
8+
# Either input your API key below, or set an environment variable
9+
# for example, in Terminal on macOS: export MERAKI_DASHBOARD_API_KEY=093b24e85df15a3e66f1fc359f4c48493eaa1b73
10+
api_key = ""
11+
12+
13+
async def listNetworkClients(aiomeraki: meraki.aio.AsyncDashboardAPI, folder_name, network):
14+
print(f'Finding clients in network {network["name"]}')
15+
try:
16+
# Get list of clients on network, filtering on timespan of last 14 days
17+
clients = await aiomeraki.clients.getNetworkClients(
18+
network["id"],
19+
timespan=60 * 60 * 24 * 14,
20+
perPage=1000,
21+
total_pages="all",
22+
)
23+
except meraki.AsyncAPIError as e:
24+
print(f"Meraki API error: {e}")
25+
except Exception as e:
26+
print(f"some other error: {e}")
27+
else:
28+
if clients:
29+
# Write to file
30+
file_name = f'{network["name"]}.csv'
31+
output_file = open(
32+
f"{folder_name}/{file_name}", mode="w", newline="\n"
33+
)
34+
field_names = clients[0].keys()
35+
csv_writer = csv.DictWriter(
36+
output_file,
37+
field_names,
38+
delimiter=",",
39+
quotechar='"',
40+
quoting=csv.QUOTE_ALL,
41+
)
42+
csv_writer.writeheader()
43+
csv_writer.writerows(clients)
44+
output_file.close()
45+
print(
46+
f"Successfully output {len(clients)} clients' data to file {file_name}"
47+
)
48+
return network["name"], field_names
49+
return network["name"], None
50+
51+
52+
async def listOrganization(aiomeraki: meraki.aio.AsyncDashboardAPI, org):
53+
print(f'Analyzing organization {org["name"]}:')
54+
org_id = org["id"]
55+
56+
# Get list of networks in organization
57+
try:
58+
networks = await aiomeraki.networks.getOrganizationNetworks(org_id)
59+
except meraki.AsyncAPIError as e:
60+
print(f"Meraki API error: {e}")
61+
return org["name"]
62+
except Exception as e:
63+
print(f"some other error: {e}")
64+
return org["name"]
65+
66+
# Create local folder
67+
todays_date = f"{datetime.now():%Y-%m-%d}"
68+
folder_name = f"Org {org_id} clients {todays_date}"
69+
if folder_name not in os.listdir():
70+
os.mkdir(folder_name)
71+
72+
# Iterate through networks
73+
total = len(networks)
74+
print(f"Iterating through {total} networks in organization {org_id}")
75+
76+
# create a list of all networks in the organization so we can call them all concurrently
77+
networkClientsTasks = [listNetworkClients(aiomeraki, folder_name, net) for net in networks]
78+
for task in asyncio.as_completed(networkClientsTasks):
79+
networkname, field_names = await task
80+
print(f"finished network: {networkname}")
81+
82+
# Stitch together one consolidated CSV per org
83+
output_file = open(f"{folder_name}.csv", mode="w", newline="\n")
84+
field_names = list(field_names)
85+
field_names.insert(0, "Network Name")
86+
field_names.insert(1, "Network ID")
87+
csv_writer = csv.DictWriter(
88+
output_file,
89+
field_names,
90+
delimiter=",",
91+
quotechar='"',
92+
quoting=csv.QUOTE_ALL,
93+
)
94+
csv_writer.writeheader()
95+
for net in networks:
96+
file_name = f'{net["name"]}.csv'
97+
if file_name in os.listdir(folder_name):
98+
with open(f"{folder_name}/{file_name}") as input_file:
99+
csv_reader = csv.DictReader(
100+
input_file,
101+
delimiter=",",
102+
quotechar='"',
103+
quoting=csv.QUOTE_ALL,
104+
)
105+
next(csv_reader)
106+
for row in csv_reader:
107+
row["Network Name"] = net["name"]
108+
row["Network ID"] = net["id"]
109+
csv_writer.writerow(row)
110+
return org["name"]
111+
112+
113+
async def main():
114+
# Instantiate a Meraki dashboard API session
115+
# NOTE: you have to use "async with" so that the session will be closed correctly at the end of the usage
116+
async with meraki.aio.AsyncDashboardAPI(
117+
api_key,
118+
base_url="https://api.meraki.com/api/v0",
119+
log_file_prefix=__file__[:-3],
120+
print_console=False,
121+
) as aiomeraki:
122+
# Get list of organizations to which API key has access
123+
organizations = await aiomeraki.organizations.getOrganizations()
124+
125+
# create a list of all organizations so we can call them all concurrently
126+
organizationTasks = [listOrganization(aiomeraki, org) for org in organizations]
127+
for task in asyncio.as_completed(organizationTasks):
128+
# as_completed returns an iterator, so we just have to await the iterator and not call it
129+
organizationName = await task
130+
print(f"finished organization: {organizationName}")
131+
132+
print("Script complete!")
133+
134+
135+
if __name__ == "__main__":
136+
loop = asyncio.get_event_loop()
137+
loop.run_until_complete(main())

examples/org_wide_clients.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44

55
import meraki
66

7-
# Either input your API key below by uncommenting line 10 and changing line 16 to api_key=api_key,
7+
# Either input your API key below by uncommenting line 10 and changing line 16 to api_key=API_KEY,
88
# or set an environment variable (preferred) to define your API key. The former is insecure and not recommended.
99
# For example, in Linux/macOS: export MERAKI_DASHBOARD_API_KEY=093b24e85df15a3e66f1fc359f4c48493eaa1b73
10-
# api_key = '093b24e85df15a3e66f1fc359f4c48493eaa1b73'
10+
# API_KEY = '093b24e85df15a3e66f1fc359f4c48493eaa1b73'
1111

1212

1313
def main():

meraki/__init__.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from .api.bluetooth_settings import BluetoothSettings
1313
from .api.camera_quality_retention_profiles import CameraQualityRetentionProfiles
1414
from .api.cameras import Cameras
15+
from .api.change_log import ChangeLog
1516
from .api.clients import Clients
1617
from .api.config_templates import ConfigTemplates
1718
from .api.connectivity_monitoring_destinations import ConnectivityMonitoringDestinations
@@ -84,8 +85,7 @@
8485
MAXIMUM_RETRIES, OUTPUT_LOG, LOG_PATH, LOG_FILE_PREFIX, PRINT_TO_CONSOLE, SIMULATE_API_CALLS
8586
)
8687

87-
__version__ = '0.80.3'
88-
88+
__version__ = '0.90.0'
8989

9090
class DashboardAPI(object):
9191
"""
@@ -100,7 +100,7 @@ class DashboardAPI(object):
100100
- output_log (boolean): create an output log file?
101101
- log_path (string): path to output log; by default, working directory of script if not specified
102102
- log_file_prefix (string): log file name appended with date and timestamp
103-
- print_console (boolean): if output log used, output to console too?
103+
- print_console (boolean): print logging output to console?
104104
- simulate (boolean): simulate POST/PUT/DELETE calls to prevent changes?
105105
"""
106106

@@ -124,13 +124,17 @@ def __init__(self, api_key=None, base_url=DEFAULT_BASE_URL, single_request_timeo
124124
level=logging.DEBUG,
125125
format='%(asctime)s %(name)12s: %(levelname)8s > %(message)s',
126126
datefmt='%Y-%m-%d %H:%M:%S')
127-
128127
if print_console:
129128
console = logging.StreamHandler()
130129
console.setLevel(logging.INFO)
131130
formatter = logging.Formatter('%(name)12s: %(levelname)8s > %(message)s')
132131
console.setFormatter(formatter)
133132
logging.getLogger('').addHandler(console)
133+
elif print_console:
134+
logging.basicConfig(
135+
level=logging.DEBUG,
136+
format='%(asctime)s %(name)12s: %(levelname)8s > %(message)s',
137+
datefmt='%Y-%m-%d %H:%M:%S')
134138

135139
# Creates the API session
136140
self._session = RestSession(
@@ -153,6 +157,7 @@ def __init__(self, api_key=None, base_url=DEFAULT_BASE_URL, single_request_timeo
153157
self.bluetooth_settings = BluetoothSettings(self._session)
154158
self.camera_quality_retention_profiles = CameraQualityRetentionProfiles(self._session)
155159
self.cameras = Cameras(self._session)
160+
self.change_log = ChangeLog(self._session)
156161
self.clients = Clients(self._session)
157162
self.config_templates = ConfigTemplates(self._session)
158163
self.connectivity_monitoring_destinations = ConnectivityMonitoringDestinations(self._session)

0 commit comments

Comments
 (0)