# This is a script to manage administrator accounts across organizations. # # To run the script, enter: # python manageadmins.py -k <api key> -o <org> -c <command> [-a <admin email> -n <admin name> -p <privilege>] # # Mandatory arguments: # -k <api key> : Your Meraki Dashboard API key # -o <organization> : Dashboard organizations in scope. Valid forms: # -o <org name> Organizations with matching name. Use * for wildcard (one * only) # -o /all All organizations accessible by your API key # -c <command> : Command to be executed. Valid forms: # -c add Add an administrator # -c delete Delete an administrator # -c find Find organizations in scope accessible by a specific admin # -c list List administrators # # Optional arguments: # -a <admin email> : Email of admin account to be added/deleted/matched. Required for commands add, delete and find # -n <admin name> : Name for admin to be added by the "add" command. Required for "add". # -p <privilege level> : Privilege level for admin to be added by the "add" command. Default is "full". Valid options: # -p full Full organization admin # -p read-only Read only organization admin # # Example, remove admin "miles.meraki@ikarem.net" from all organizations: # python manageadmins.py -k 1234 -o /all -c delete -a miles.meraki@ikarem.net # Example, add admin "miles.meraki@ikarem.net" to all organizations with a name starting with "TIER1_": # python manageadmins.py -k 1234 -o TIER1_* -c add -a miles.meraki@ikarem.net -n Miles # # This script was developed using Python 3.6.4. You will need the Requests module to run it. You can install # it modules via pip: # pip install requests # # More info on this module: # http://python-requests.org # # To make script chaining easier, all lines containing informational messages to the user # start with the character @ # # This file was last modified on 2018-12-11 by Mihail Papazoglou import sys, getopt, requests, json, time from datetime import datetime #Used for time.sleep(API_EXEC_DELAY). Delay added to avoid hitting dashboard API max request rate API_EXEC_DELAY = 0.21 #connect and read timeouts for the Requests module REQUESTS_CONNECT_TIMEOUT = 60 REQUESTS_READ_TIMEOUT = 60 #used by merakirequestthrottler(). DO NOT MODIFY LAST_MERAKI_REQUEST = datetime.now() class c_orgdata: def __init__(self): self.id = '' self.name = '' self.shard = '' #Meraki cloud shard where this org is stored def printusertext(p_message): #prints a line of text that is meant for the user to read #do not process these lines when chaining scripts print('@ %s' % p_message) def printhelp(): #prints help text printusertext('This is a script to manage administrator accounts across organizations.') printusertext('') printusertext('To run the script, enter:') printusertext('python manageadmins.py -k <api key> -o <org> -c <command> [-a <admin email> -n <admin name> -p <privilege>]') printusertext('') printusertext('Mandatory arguments:') printusertext(' -k <api key> : Your Meraki Dashboard API key') printusertext(' -o <organization> : Dashboard organizations in scope. Valid forms:') printusertext(' -o <org name> Organizations with matching name. Use * for wildcard (one * only)') printusertext(' -o /all All organizations accessible by your API key') printusertext(' -c <command> : Command to be executed. Valid forms:') printusertext(' -c add Add an administrator') printusertext(' -c delete Delete an administrator') printusertext(' -c find Find organizations in scope accessible by a specific admin') printusertext(' -c list List administrators') printusertext('') printusertext('Optional arguments:') printusertext(' -a <admin email> : Email of admin account to be added/deleted/matched. Required for add, delete and find') printusertext(' -n <admin name> : Name for admin to be added by the "add" command. Required for "add".') printusertext(' -p <privilege level> : Privilege level for admin to be added by the "add" command. Default is "full". Valid options:') printusertext(' -p full Full organization admin') printusertext(' -p read-only Read only organization admin') printusertext('') printusertext('Example, remove admin "miles.meraki@ikarem.net" from all organizations:') printusertext(' python manageadmins.py -k 1234 -o /all -c delete -a miles.meraki@ikarem.net') printusertext('Example, add admin "miles.meraki@ikarem.net" to all organizations with a name starting with "TIER1_":') printusertext(' python manageadmins.py -k 1234 -o TIER1_* -c add -a miles.meraki@ikarem.net -n Miles') printusertext('') printusertext('Use double quotes ("") in Windows to pass arguments containing spaces. Names are case-sensitive.') def merakirequestthrottler(p_requestcount=1): #makes sure there is enough time between API requests to Dashboard to avoid hitting shaper global LAST_MERAKI_REQUEST if (datetime.now()-LAST_MERAKI_REQUEST).total_seconds() < (API_EXEC_DELAY*p_requestcount): time.sleep(API_EXEC_DELAY*p_requestcount) LAST_MERAKI_REQUEST = datetime.now() return def getorglist(p_apikey): #returns the organizations' list for a specified admin merakirequestthrottler() try: r = requests.get('https://api.meraki.com/api/v0/organizations', headers={'X-Cisco-Meraki-API-Key': p_apikey, 'Content-Type': 'application/json'}, timeout=(REQUESTS_CONNECT_TIMEOUT, REQUESTS_READ_TIMEOUT)) except: printusertext('ERROR 01: Unable to contact Meraki cloud') sys.exit(2) returnvalue = [] if r.status_code != requests.codes.ok: returnvalue.append({'id':'null'}) return returnvalue rjson = r.json() return(rjson) def getorgadmins(p_apikey, p_org, p_shardhost): #returns the list of admins for a specified organization merakirequestthrottler() try: r = requests.get('https://%s/api/v0/organizations/%s/admins' % (p_shardhost, p_org.id), headers={'X-Cisco-Meraki-API-Key': p_apikey, 'Content-Type': 'application/json'}, timeout=(REQUESTS_CONNECT_TIMEOUT, REQUESTS_READ_TIMEOUT)) except: printusertext('ERROR 02: Unable to contact Meraki cloud') sys.exit(2) returnvalue = [] if r.status_code != requests.codes.ok: returnvalue.append({'id':'null'}) printusertext('WARNING: Unable to get admin list for organization "%s"' % p_org.name) return returnvalue rjson = r.json() return(rjson) def addorgadmin(p_apikey, p_orgid, p_shardurl, p_email, p_name, p_privilege): #creates admin into an organization merakirequestthrottler() try: r = requests.post('https://%s/api/v0/organizations/%s/admins' % (p_shardurl, p_orgid), data=json.dumps({'name': p_name, 'email': p_email, 'orgAccess': p_privilege}), headers={'X-Cisco-Meraki-API-Key': p_apikey, 'Content-Type': 'application/json'}, timeout=(REQUESTS_CONNECT_TIMEOUT, REQUESTS_READ_TIMEOUT)) except: printusertext('ERROR 03: Unable to contact Meraki cloud') sys.exit(2) if r.status_code != requests.codes.ok: if r.status_code == 400: printusertext('WARNING: Email already registered with a Cisco Meraki Dashboard account. For security purposes, that user must verify their email address before administrator permissions can be granted here.') return ('fail') return('ok') def deleteorgadmin(p_apikey, p_orgid, p_shardhost, p_adminid): #removes an administrator from an organization merakirequestthrottler() try: r = requests.delete('https://%s/api/v0/organizations/%s/admins/%s' % (p_shardhost, p_orgid, p_adminid), headers={'X-Cisco-Meraki-API-Key': p_apikey, 'Content-Type': 'application/json'}, timeout=(REQUESTS_CONNECT_TIMEOUT, REQUESTS_READ_TIMEOUT)) except: printusertext('ERROR 04: Unable to contact Meraki cloud') sys.exit(2) returnvalue = [] if r.status_code != requests.codes.ok: return ('fail') return ('ok') def findadminid(p_adminlist, p_adminemail): #returns admin id associated with an email or 'null', if it is not found if p_adminlist[0]['id'] != 'null': for admin in p_adminlist: if admin['email'] == p_adminemail: return (admin['id']) return('null') def filterorglist(p_apikey, p_filter, p_orglist): #tried to match a list of org IDs to a filter expression # /all all organizations # <name> match if name matches. name can contain one wildcard at start, middle or end returnlist = [] flag_processall = False flag_gotwildcard = False if p_filter == '/all': flag_processall = True else: wildcardpos = p_filter.find('*') if wildcardpos > -1: flag_gotwildcard = True startsection = '' endsection = '' if wildcardpos == 0: #wildcard at start of string, only got endsection endsection = p_filter[1:] elif wildcardpos == len(p_filter) - 1: #wildcard at start of string, only got startsection startsection = p_filter[:-1] else: #wildcard at middle of string, got both startsection and endsection wildcardsplit = p_filter.split('*') startsection = wildcardsplit[0] endsection = wildcardsplit[1] for org in p_orglist: if flag_processall: returnlist.append(c_orgdata()) returnlist[len(returnlist)-1].id = org['id'] returnlist[len(returnlist)-1].name = org['name'] elif flag_gotwildcard: flag_gotmatch = True #match startsection and endsection startlen = len(startsection) endlen = len(endsection) if startlen > 0: if org['name'][:startlen] != startsection: flag_gotmatch = False if endlen > 0: if org['name'][-endlen:] != endsection: flag_gotmatch = False if flag_gotmatch: returnlist.append(c_orgdata()) returnlist[len(returnlist)-1].id = org['id'] returnlist[len(returnlist)-1].name = org['name'] else: #match full name if org['name'] == p_filter: returnlist.append(c_orgdata()) returnlist[len(returnlist)-1].id = org['id'] returnlist[len(returnlist)-1].name = org['name'] return(returnlist) def cmdadd(p_apikey, p_orgs, p_email, p_name, p_privilege): #creates an administrator in all orgs in scope if p_privilege not in ['full', 'read-only']: printusertext('ERROR 09: Unsupported privilege level "%s"' % p_privilege) sys.exit(2) for org in p_orgs: orgadmins = getorgadmins(p_apikey, org, 'api.meraki.com') adminid = findadminid(orgadmins, p_email) if adminid != 'null': printusertext('INFO: Skipping org "%s". Admin already exists' % org.name) else: printusertext('INFO: Creating admin "%s" in org "%s"' % (p_email, org.name)) addorgadmin(p_apikey, org.id, 'api.meraki.com', p_email, p_name, p_privilege) #verify that admin was correctly created orgadmins = getorgadmins(p_apikey, org, 'api.meraki.com') adminid = findadminid(orgadmins, p_email) if adminid == 'null': printusertext('WARNING: Unable to create admin "%s" in org "%s"' % (p_email, org.name)) return(0) def cmddelete(p_apikey, p_orgs, p_admin): #deletes an administrator from all orgs in scope for org in p_orgs: orgadmins = getorgadmins(p_apikey, org, 'api.meraki.com') adminid = findadminid(orgadmins, p_admin) if adminid != 'null': printusertext('INFO: Removing admin "%s" from org "%s"' % (p_admin, org.name)) deleteorgadmin(p_apikey, org.id, 'api.meraki.com', adminid) else: printusertext('INFO: Admin "%s" cannot be found in org "%s"' % (p_admin, org.name)) #verify that the admin has actually been deleted orgadmins = getorgadmins(p_apikey, org, 'api.meraki.com') adminid = findadminid(orgadmins, p_admin) if adminid != 'null': printusertext('WARNING: Unable to remove admin "%s" from org "%s"' % (p_admin, org.name)) return(0) def cmdfind(p_apikey, p_orgs, p_admin): #finds organizations that contain an admin with specified email for org in p_orgs: orgadmins = getorgadmins(p_apikey, org, 'api.meraki.com') adminid = findadminid(orgadmins, p_admin) if adminid != 'null': print('Found admin "%s" in org "%s"' % (p_admin, org.name)) return(0) def cmdlist(p_apikey, p_orgs): #lists all admins in specified orgs for org in p_orgs: orgadmins = getorgadmins(p_apikey, org, 'api.meraki.com') if orgadmins[0]['id'] != 'null': print('\nAdministrators for org "%s"' % org.name) print('NAME EMAIL ORG PRIVILEGE') for admin in orgadmins: print('%-30s %-50s %-20s' % (admin['name'], admin['email'], admin['orgAccess'])) return(0) def main(argv): #initialize variables for command line arguments arg_apikey = '' arg_orgname = '' arg_admin = '' arg_command = '' arg_name = '' arg_privilege = '' #get command line arguments try: opts, args = getopt.getopt(argv, 'hk:o:c:a:n:p:') except getopt.GetoptError: printhelp() sys.exit(2) for opt, arg in opts: if opt == '-h': printhelp() sys.exit() elif opt == '-k': arg_apikey = arg elif opt == '-o': arg_orgname = arg elif opt == '-c': arg_command = arg elif opt == '-a': arg_admin = arg elif opt == '-n': arg_name = arg elif opt == '-p': arg_privilege = arg #check if all parameters are required parameters have been given if arg_apikey == '' or arg_orgname == '' or arg_command == '': printhelp() sys.exit(2) #fail invalid commands quickly, not to annoy user cleancmd = arg_command.lower().strip() if cleancmd not in ['add', 'delete', 'find', 'list']: printusertext('ERROR 05: Invalid command "%s"' % cleancmd) sys.exit(2) if arg_admin == '' and cleancmd != 'list': printusertext('ERROR 06: Command "%s" needs parameter -a <admin account>' % arg_command) sys.exit(2) if cleancmd == 'add' and arg_name == '': printusertext('ERROR 07: Command "add" needs parameter -n <name>') sys.exit(2) #set default values for optional arguments if arg_privilege == '': arg_privilege = 'full' #build list of organizations to be processed #get list org all orgs belonging to this admin raworglist = getorglist(arg_apikey) if raworglist[0]['id'] == 'null': printusertext('ERROR 08: Error retrieving organization list') sys.exit(2) #match list of orgs to org filter matchedorgs = filterorglist(arg_apikey, arg_orgname, raworglist) #launch correct command code if cleancmd == 'add': cmdadd(arg_apikey, matchedorgs, arg_admin, arg_name, arg_privilege) elif cleancmd == 'delete': cmddelete(arg_apikey, matchedorgs, arg_admin) elif cleancmd == 'find': cmdfind(arg_apikey, matchedorgs, arg_admin) elif cleancmd == 'list': cmdlist(arg_apikey, matchedorgs) if __name__ == '__main__': main(sys.argv[1:])