def main(): """ This script will monitor device configuration changes. It could be executed on demand, periodically (every 60 minutes, for example) or continuously. It will collect the configuration file for each DNA Center managed device, compare with the existing cached file, and detect if any changes. When changes detected, identify the last user that configured the device, and create a new ServiceNow incident. Automatically roll back all non-compliant configurations, or save new configurations if approved in ServiceNow. Device configuration files managemnt using RESTCONF and NETCONF Compliance checks at this time: - no Access Control Lists changes - no logging changes - no duplicated IPv4 addresses """ # run the demo continuously, looping print('Application config_mon.py started') # create a local directory for all the configuration files # check if 'Config_Files' folder exists and create one if it does not if not os.path.exists('Config_Files'): os.makedirs('Config_Files') os.chdir('Config_Files') # logging, debug level, to file {application_run.log} logging.basicConfig( filename='application_run.log', level=logging.DEBUG, format= '%(asctime)s.%(msecs)03d %(levelname)s %(module)s - %(funcName)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S') while True: # get the DNA C auth token dnac_token = dnac_apis.get_dnac_jwt_token(DNAC_AUTH) print('\nDNA C AUTH TOKEN: ', dnac_token, '\n') temp_run_config = 'temp_run_config.txt' # get the DNA C managed devices list (excluded wireless, for one location) all_devices_info = dnac_apis.get_all_device_info(dnac_token) all_devices_hostnames = [] for device in all_devices_info: if device['family'] == 'Switches and Hubs' or device[ 'family'] == 'Routers': if IOS_XE_HOSTNAME in device['hostname']: all_devices_hostnames.append(device['hostname']) # get the config files, compare with existing (if one existing). Save new config if file not existing. for device in all_devices_hostnames: device_run_config = dnac_apis.get_output_command_runner( 'show running-config', device, dnac_token) filename = str(device) + '_run_config.txt' # save the running config to a temp file f_temp = open(temp_run_config, 'w') f_temp.write(device_run_config) f_temp.seek(0) # reset the file pointer to 0 f_temp.close() # check if device has an existing configuration file (to account for newly discovered DNA C devices) # if yes, run the diff function # if not, save the device configuration to the local device database # this will create the local "database" of configs, one file/device if os.path.isfile(filename): diff = compare_configs(filename, temp_run_config) if diff != '': # retrieve the device location using DNA C REST APIs location = dnac_apis.get_device_location( device, dnac_token) # find the users that made configuration changes with open(temp_run_config, 'r') as f: user_info = 'User info no available' for line in f: if 'Last configuration change' in line: user_info = line # define the incident description and comment short_description = 'Configuration Change Alert - ' + device comment = 'The device with the name: ' + device + '\nhas detected a Configuration Change' print(comment) # create ServiceNow incident using ServiceNow APIs incident = service_now_apis.create_incident( short_description, comment, SNOW_DEV, 3) # get the device health from DNA Center current_time_epoch = utils.get_epoch_current_time() device_details = dnac_apis.get_device_health( device, current_time_epoch, dnac_token) device_sn = device_details['serialNumber'] private_mngmnt_ip_address = device_details[ 'managementIpAddr'] device_mngmnt_ip_address = NAT[private_mngmnt_ip_address] device_family = device_details['platformId'] device_os_info = device_details[ 'osType'] + ', ' + device_details['softwareVersion'] device_health = device_details['overallHealth'] updated_comment = '\nDevice location: ' + location updated_comment += '\nDevice family: ' + device_family updated_comment += '\nDevice OS info: ' + device_os_info updated_comment += '\nDevice S/N: ' + device_sn updated_comment += '\nDevice Health: ' + str( device_health) + '/10' updated_comment += '\nDevice management IP address: ' + device_mngmnt_ip_address print(updated_comment) # update ServiceNow incident service_now_apis.update_incident(incident, updated_comment, SNOW_DEV) updated_comment = '\nThe configuration changes are\n' + diff + '\n\n' + user_info print(updated_comment) # update ServiceNow incident service_now_apis.update_incident(incident, updated_comment, SNOW_DEV) # start the compliance validation # ACL changes validation_result = 'Pass' validation_comment = '' if ('+access-list' in diff) or ('-access-list' in diff): updated_comment = '\nValidation against ACL changes failed' service_now_apis.update_incident( incident, updated_comment, SNOW_DEV) validation_result = 'Failed' else: validation_comment = '\nPassed ACL Policy' # logging changes if ('+logging' in diff) or ('-logging' in diff): updated_comment = '\nValidation against logging changes failed' service_now_apis.update_incident( incident, updated_comment, SNOW_DEV) validation_result = 'Failed' else: validation_comment += '\nPassed Logging Policy' # IPv4 duplicates diff_list = diff.split('\n') diff_config = '!\n' for command in diff_list: if 'ip address' in command: diff_config += command.replace('+', '') + '\n!' # save the diff config that include only IP addresses in a file f_diff = open('temp_config_file.txt', 'w') f_diff.write(diff_config) f_diff.seek(0) # reset the file pointer to 0 f_diff.close() duplicate_ip_result = dnac_apis.check_ipv4_duplicate( 'temp_config_file.txt') if duplicate_ip_result: updated_comment = '\nValidation against duplicated IPv4 addresses failed' service_now_apis.update_incident( incident, updated_comment, SNOW_DEV) validation_result = 'Failed' else: validation_comment += '\nPassed Duplicate IPv4 Prevention' print(updated_comment) # procedure to restore configurations as policy validations failed if validation_result == 'Failed': updated_comment = '\nConfiguration changes do not pass validation,\nConfiguration roll back initiated' service_now_apis.update_incident( incident, updated_comment, SNOW_DEV) print(updated_comment) # start the config roll back baseline_config = 'flash:/config_mon_baseline' netconf_restconf.restconf_rollback_to_saved_config( baseline_config, device_mngmnt_ip_address, IOS_XE_USER, IOS_XE_PASS) # check if rollback is successful after 5 seconds time.sleep(5) device_run_config = dnac_apis.get_output_command_runner( 'show running-config', device, dnac_token) filename = str(device) + '_run_config.txt' # save the running config to a temp file f_temp = open(temp_run_config, 'w') f_temp.write(device_run_config) f_temp.seek(0) # reset the file pointer to 0 f_temp.close() diff = compare_configs(filename, temp_run_config) if diff != ' ': updated_comment = '\nConfiguration rolled back successfully' service_now_apis.update_incident( incident, updated_comment, SNOW_DEV) # close ServiceNow incident service_now_apis.close_incident(incident, SNOW_DEV) else: updated_comment = '\nConfiguration rolled back not successful' service_now_apis.update_incident( incident, updated_comment, SNOW_DEV) # start procedure to ask for approval as validation passed else: service_now_apis.update_incident( incident, 'Approve these changes (YES/NO)?\n' + validation_comment, SNOW_DEV) service_now_apis.update_incident( incident, 'Waiting for Management Approval', SNOW_DEV) print( '\nConfiguration changes pass compliance checks, approval waiting' ) # start the approval YES/NO procedure # start a loop to check for 2 min if approved of not approval = 'NO' timer_count = 0 while timer_count <= 5: if service_now_apis.find_comment(incident, 'YES'): # start the save of running config to new baseline on device # flash:/config_mon_baseline netconf_restconf.netconf_save_running_config_to_file( 'flash:/config_mon_baseline', device_mngmnt_ip_address, IOS_XE_PORT, IOS_XE_USER, IOS_XE_PASS) print( 'New baseline configuration saved on network device: ' + device) # establish new baseline config on server time.sleep(5) device_run_config = dnac_apis.get_output_command_runner( 'show running-config', device, dnac_token) filename = str(device) + '_run_config.txt' # save the running config to the device config file f_temp = open(filename, 'w') f_temp.write(device_run_config) f_temp.seek(0) # reset the file pointer to 0 f_temp.close() approval = 'YES' # update ServiceNow incident updated_comment = '\nApproval received, saved device configuration, established new baseline configuration' print(updated_comment) service_now_apis.update_incident( incident, updated_comment, SNOW_DEV) service_now_apis.close_incident( incident, SNOW_DEV) break elif service_now_apis.find_comment(incident, 'NO'): break else: timer_count += 1 time.sleep(10) if timer_count == 5: service_now_apis.update_incident( incident, 'Approval Timeout', SNOW_DEV) # check if Approval is 'NO' at the end of the timer if approval == 'NO': # start the config roll back baseline_config = 'flash:/config_mon_baseline' netconf_restconf.restconf_rollback_to_saved_config( baseline_config, device_mngmnt_ip_address, IOS_XE_USER, IOS_XE_PASS) # check if rollback is successful after 3 seconds time.sleep(5) device_run_config = dnac_apis.get_output_command_runner( 'show running-config', device, dnac_token) # save the running config to a temp file f_temp = open(temp_run_config, 'w') f_temp.write(device_run_config) f_temp.seek(0) # reset the file pointer to 0 f_temp.close() filename = str(device) + '_run_config.txt' diff = compare_configs(filename, temp_run_config) if diff != ' ': updated_comment = '\nConfiguration changes not approved,\nConfiguration rolled back successfully' print(updated_comment) service_now_apis.update_incident( incident, updated_comment, SNOW_DEV) service_now_apis.close_incident( incident, SNOW_DEV) else: updated_comment = '\nConfiguration changes not approved,\nConfiguration rolled back not successful' service_now_apis.update_incident( incident, updated_comment, SNOW_DEV) else: print('Device: ' + device + ' - No configuration changes detected') else: # new device discovered, save the running configuration to a file in the folder with the name # {Config_Files} f_config = open(filename, 'w') f_config.write(device_run_config) f_config.seek(0) f_config.close() # retrieve the device management IP address private_mngmnt_ip_address = dnac_apis.get_device_management_ip( device, dnac_token) device_mngmnt_ip_address = NAT[private_mngmnt_ip_address] # Save the running configuration as a baseline configuration local on each device # flash:/config_mon_baseline netconf_restconf.netconf_save_running_config_to_file( 'flash:/config_mon_baseline', device_mngmnt_ip_address, IOS_XE_PORT, IOS_XE_USER, IOS_XE_PASS) print('Device: ' + device + ' - New device discovered') print('Wait for 10 seconds and start again') time.sleep(10) print('\n\nEnd of Application Run')
def main(): """ Vendor will join Webex Teams Room with the name {ROOM_NAME} It will ask for access to an IP-enabled device - named {IPD} The code will map this IP-enabled device to the IP address {10.93.140.35} Access will be provisioned to allow connectivity from DMZ VDI to IPD """ # save the initial stdout initial_sys = sys.stdout # the user will be asked if interested to run in demo mode or in # production (logging to files - erna_log.log, erna_err.log)) # user_input = utils.get_input_timeout('If running in Demo Mode please enter y ', 10) user_input = 'y' # remove this line if you want to run in production if user_input != 'y': # open a log file 'erna.log' file_log = open('erna_log.log', 'w') # open an error log file 'erna_err.log' err_log = open('erna_err.log', 'w') # redirect the stdout to file_log and err_log sys.stdout = file_log sys.stderr = err_log # configure basic logging to send to stdout, level DEBUG, include timestamps logging.basicConfig(level=logging.DEBUG, stream=sys.stdout, format='%(asctime)s - %(levelname)s - %(message)s') # logging, debug level, to file {erna_run.log} logging.basicConfig( filename='erna_run.log', level=logging.DEBUG, format= '%(asctime)s.%(msecs)03d %(levelname)s %(module)s - %(funcName)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S') # the local date and time when the code will start execution date_time = str(datetime.datetime.now().replace(microsecond=0)) print('\nThe Application "ERNA.py" started running at this time ' + date_time) user_input = utils.get_input_timeout('Enter y to skip next section : ', 10) user_input = 'y' # remove this line if you want to follow the approval process if user_input != 'y': # verify if Webex Teams Space exists, if not create Webex Teams Space, and add membership (optional) webex_teams_room_id = webex_teams_apis.get_space_id(ROOM_NAME) if webex_teams_room_id is None: webex_teams_room_id = webex_teams_apis.create_space(ROOM_NAME) print('- ', ROOM_NAME, ' - Webex Teams room created') # invite membership to the room webex_teams_apis.add_space_membership(ROOM_NAME, APPROVER_EMAIL) webex_teams_apis.post_space_message( ROOM_NAME, 'To require access enter : IPD') webex_teams_apis.post_space_message(ROOM_NAME, 'Ready for input!') print('Instructions posted in the room') else: print('- ', ROOM_NAME, ' - Existing Webex Teams room found') webex_teams_apis.post_space_message( ROOM_NAME, 'To require access enter : IPD') webex_teams_apis.post_space_message(ROOM_NAME, 'Ready for input!') print('- ', ROOM_NAME, ' - Webex Teams room id: ', webex_teams_room_id) # check for messages to identify the last message posted and the user's email who posted the message # check for the length of time required for access last_message = (webex_teams_apis.last_user_message(ROOM_NAME))[0] while last_message == 'Ready for input!': time.sleep(5) last_message = (webex_teams_apis.last_user_message(ROOM_NAME))[0] if last_message == 'IPD': last_person_email = ( webex_teams_apis.last_user_message(ROOM_NAME))[1] webex_teams_apis.post_space_message( ROOM_NAME, 'How long time do you need access for? (in minutes) : ') time.sleep(10) if ( webex_teams_apis.last_user_message(ROOM_NAME) )[0] == 'How long time do you need access for? (in minutes) : ': timer = 30 * 60 else: timer = int( webex_teams_apis.last_user_message(ROOM_NAME)[0]) * 60 elif last_message != 'Ready for input!': webex_teams_apis.post_space_message(ROOM_NAME, 'I do not understand you') webex_teams_apis.post_space_message( ROOM_NAME, 'To require access enter : IPD') webex_teams_apis.post_space_message(ROOM_NAME, 'Ready for input!') last_message = 'Ready for input!' print('\nThe user with this email: ', last_person_email, ' asked access to IPD for ', (timer / 60), ' minutes') # get the WJT Auth token to access DNA dnac_token = dnac_apis.get_dnac_jwt_token(DNAC_AUTH) print('\nThe DNA Center auth token is: ', dnac_token) # IPD IP address - DNS lookup if available IPD_IP = '10.93.140.35' last_person_email = '*****@*****.**' approver_email = '*****@*****.**' timer = 3600 # locate IPD in the environment using DNA C ipd_location_info = dnac_apis.locate_client_ip(IPD_IP, dnac_token) remote_device_hostname = ipd_location_info[ 0] # the network device the IPD is connected to vlan_number = ipd_location_info[2] # the access VLAN number interface_name = ipd_location_info[ 1] # the interface number is connected to device_location = dnac_apis.get_device_location( remote_device_hostname, dnac_token) # network device location location_list_info = device_location.split('/') remote_device_location = location_list_info[-1] # select the building name log_ipd_info = '\n\nThe IPD is connected to this device: ' + remote_device_hostname log_ipd_info += '\n\nThis interface: ' + interface_name + ', access VLAN: ' + vlan_number log_ipd_info += '\n\nLocated : ' + remote_device_location print(log_ipd_info) # request approval if user_input != 'y': webex_teams_apis.post_space_message( ROOM_NAME, ('The user with this email ' + last_person_email + ' asked access to IPD for ' + str(timer / 60) + ' minutes')) webex_teams_apis.post_space_message( ROOM_NAME, 'The IPD is connected to the switch ' + remote_device_hostname + ' at our location ' + remote_device_location) webex_teams_apis.post_space_message(ROOM_NAME, 'To approve enter: Y/N') # check for messages to identify the last message posted and the user's email who posted the message. # looking for user - Director email address, and message = 'Y' last_message = (webex_teams_apis.last_user_message(ROOM_NAME))[0] while last_message == 'To approve enter: Y/N': time.sleep(5) last_message = (webex_teams_apis.last_user_message(ROOM_NAME))[0] approver_email = (webex_teams_apis.last_user_message(ROOM_NAME))[1] if last_message == 'y' or 'Y': if approver_email == APPROVER_EMAIL: print('Access Approved') else: last_message = 'To approve enter: Y/N' print('\nApproval process completed') # get UCSD API key # ucsd_key = ucsd_apis.get_ucsd_api_key() # execute UCSD workflow to connect VDI to VLAN, power on VDI # ucsd_apis.execute_ucsd_workflow(ucsd_key, UCSD_CONNECT_FLOW) log_ucsd_info = '\n\nUCSD connect flow executed' print(log_ucsd_info) # deployment of cli configuration files to the dc router dc_device_hostname = 'PDX-RO' template_project = 'ERNA' print('\nThe DC device name is: ', dc_device_hostname) dc_config_file = 'DC_Config.txt' dc_config_templ = dc_config_file.split('.')[ 0] # select the template name from the template file cli_file = open(dc_config_file, 'r') # open file with the template cli_config = cli_file.read() # read the file # validation of dc router cli template dc_valid = dnac_apis.check_ipv4_duplicate(dc_config_file) log_dc_info = '' if dc_valid is False: print('\n\nDC Router CLI Template validated') log_dc_info = '\n\nDC Router CLI Templates validated' dnac_apis.upload_template(dc_config_templ, template_project, cli_config, dnac_token) # upload the template to DNA C depl_id_dc = dnac_apis.deploy_template(dc_config_templ, template_project, dc_device_hostname, dnac_token) # deploy dc template log_dc_info += '\n\nDeployment of the configurations to the DC router, ' + dc_device_hostname log_dc_info += ' started, deployment id: ' + depl_id_dc log_dc_config = '\nDC Router Config \n\n' + cli_config print(log_dc_info) time.sleep(1) # deployment of cli configuration files to the remote router remote_config_file = 'Remote_Config.txt' remote_config_templ = remote_config_file.split('.')[ 0] # select the template name from the template file cli_file = open(remote_config_file, 'r') # open file with the template cli_config = cli_file.read() # read the file # update the template with the localized info for the IPD # replace the $VlanId with the localized VLAN access # replace the $IPD with the IPD ip address cli_config = cli_config.replace('$IPD', IPD_IP) cli_config = cli_config.replace('$VlanId', vlan_number) remote_updated_config_file = 'Remote_Config_Updated.txt' updated_cli_file = open(remote_updated_config_file, 'w') updated_cli_file.write(cli_config) updated_cli_file.close() # validation of remote router cli template remote_valid = dnac_apis.check_ipv4_duplicate(remote_updated_config_file) log_remote_info = '' if remote_valid is False: log_remote_info = '\n\nRemote Device CLI Template validated' print(log_remote_info) dnac_apis.upload_template(remote_config_templ, template_project, cli_config, dnac_token) # upload the template to DNA C depl_id_remote = dnac_apis.deploy_template( remote_config_templ, template_project, remote_device_hostname, dnac_token) # deploy remote template time.sleep(1) log_remote_info += '\n\nDeployment of the configurations to the Remote device, ' + remote_device_hostname log_remote_info += ' started, deployment id: ' + depl_id_remote log_remote_config = '\nRemote Device Config \n\n' + cli_config print(log_remote_info) time.sleep(1) # check the deployment status after waiting for all jobs to complete - 10 seconds print('\nWait for DNA Center to complete template deployments') time.sleep(10) dc_status = dnac_apis.check_template_deployment_status( depl_id_dc, dnac_token) remote_status = dnac_apis.check_template_deployment_status( depl_id_remote, dnac_token) log_templ_depl_info = '\n\nTemplates deployment status DC: ' + dc_status + ', Remote: ' + remote_status print(log_templ_depl_info) if dc_status == 'SUCCESS' and remote_status == 'SUCCESS': print('\nAll templates deployment have been successful\n') templ_deploy_status = True # synchronization of devices configured - DC and Remote Router dc_sync_status = dnac_apis.sync_device(dc_device_hostname, dnac_token)[0] remote_sync_status = dnac_apis.sync_device(remote_device_hostname, dnac_token)[0] if dc_sync_status == 202: print('\nDNA Center started the DC Router resync') if remote_sync_status == 202: print('\nDNA Center started the Remote Router resync') dc_router_tunnel = netconf_restconf.get_restconf_int_oper_status( 'Tunnel201') remote_router_tunnel = netconf_restconf.get_netconf_int_oper_status( 'Tunnel201') log_tunnel_info = '\n\nThe Tunnel 201 interfaces operational state: ' log_tunnel_info += '\n\nFrom ' + remote_device_hostname + ' using NETCONF - ' + dc_router_tunnel log_tunnel_info += '\n\nFrom ' + dc_device_hostname + ' using RESTCONF - ' + remote_router_tunnel print(log_tunnel_info) print('\nWait for DNA Center to complete the resync of the two devices') time.sleep(180) print('\nSync completed, Path Trace started') # start a path trace to check the path segmentation path_trace_id = dnac_apis.create_path_trace('172.16.202.1', IPD_IP, dnac_token) print('\nWait for Path Trace to complete') time.sleep(30) path_trace_info = dnac_apis.get_path_trace_info(path_trace_id, dnac_token) log_path_trace = '\n\nPath Trace status: ' + path_trace_info[0] log_path_trace += '\n\nPath Trace details: ' + str(path_trace_info[1]) print(log_path_trace) # create ASAv outside interface ACL to allow traffic outside_acl_id = asav_apis.get_asav_access_list(OUTSIDE_INT) asav_status = asav_apis.create_asav_access_list(outside_acl_id, OUTSIDE_INT, VDI_IP, IPD_IP) if asav_status == 201: log_asav_info = '\n\nASAv access list updated to allow traffic from ' + VDI_IP + ' to ' + IPD_IP + ' on the interface ' + OUTSIDE_INT else: log_asav_info = '\n\nError updating the ASAv access list on the interface ' + OUTSIDE_INT print(log_asav_info) # Webex Teams notification webex_teams_apis.post_space_message( ROOM_NAME, 'Requested access to this device: IPD, located in our office: ' + remote_device_location + ' by user ' + last_person_email + ' has been granted for ' + str(int(timer / 60)) + ' minutes') log_access_info = '\n\nRequested access to this device: IPD, located in our office: ' log_access_info += remote_device_location + ' by user: '******' has been granted for ' + str( int(timer / 60)) + ' minutes' # create and update ServiceNow incident snow_user = '******' snow_description = 'ERNA Automation - Vendor Remote Access to IPD: ' + IPD_IP snow_incident = service_now_apis.create_incident(snow_description, log_ipd_info, snow_user, '2') comments = log_ucsd_info + log_dc_info + log_dc_config + log_remote_info + log_remote_config + log_templ_depl_info comments += log_tunnel_info + log_path_trace + log_asav_info + log_access_info service_now_apis.update_incident(snow_incident, comments, snow_user) date_time = str(datetime.datetime.now().replace(microsecond=0)) print('\n\nEnd of application "ERNA.py" provisioning run at this time ', date_time) # time.sleep(timer) # un-comment this line if time limit is required input('Press any key to continue de-provisioning ') # # restore configurations to initial state # # restore DC router config dc_rem_file = 'DC_Remove.txt' dc_rem_templ = dc_rem_file.split('.')[0] cli_file = open(dc_rem_file, 'r') cli_config = cli_file.read() dnac_apis.upload_template(dc_rem_templ, template_project, cli_config, dnac_token) depl_id_dc_rem = dnac_apis.deploy_template(dc_rem_templ, template_project, dc_device_hostname, dnac_token) print('\nDC Router restored to the baseline configuration') log_remove_info = '\n\nDC Router restored to the baseline configuration' time.sleep(1) # restore Remote router config remote_rem_file = 'Remote_Remove.txt' remote_rem_templ = remote_rem_file.split('.')[0] cli_file = open(remote_rem_file, 'r') cli_config = cli_file.read() # update the template with the local info for the IPD # replace the $VlanId with the local VLAN access # replace the $IPD with the IPD ip address cli_config = cli_config.replace('$IPD', IPD_IP) cli_config = cli_config.replace('$VlanId', vlan_number) remote_updated_remove_file = 'Remote_Remove_Updated.txt' updated_cli_file = open(remote_updated_remove_file, 'w') updated_cli_file.write(cli_config) updated_cli_file.close() dnac_apis.upload_template(remote_rem_templ, template_project, cli_config, dnac_token) depl_id_remote_rem = dnac_apis.deploy_template(remote_rem_templ, template_project, remote_device_hostname, dnac_token) print('\nRemote Router restored to the baseline configuration') log_remove_info += '\n\nRemote Device restored to the baseline configuration' time.sleep(1) # remove the ASAv outside interface ACLE that allowed traffic between VDI and IPD outside_acl_id = asav_apis.get_asav_access_list(OUTSIDE_INT) asav_status = asav_apis.delete_asav_access_list(outside_acl_id, OUTSIDE_INT) if asav_status == 204: log_asav_remove_info = '\n\nASAv access list on the interface ' + OUTSIDE_INT + ' restored to the baseline configuration' else: log_asav_remove_info = 'Error while restoring the ASAv access list on the interface ' + OUTSIDE_INT print(log_asav_remove_info) # execute UCSD workflow to disconnect VDI to VLAN, power on VDI # ucsd_apis.execute_ucsd_workflow(ucsd_key, UCSD_DISCONNECT_FLOW) log_ucsd_remove_info = '\n\nUCSD disconnect flow executed' print(log_ucsd_remove_info) # sync the remote and DC device dc_sync_status = dnac_apis.sync_device(dc_device_hostname, dnac_token)[0] remote_sync_status = dnac_apis.sync_device(remote_device_hostname, dnac_token)[0] log_sync_info = '' if dc_sync_status == 202: log_sync_info = '\n\nDNA Center started the DC Router resync' if remote_sync_status == 202: log_sync_info += '\n\nDNA Center started the Remote Router resync' print(log_sync_info) # Webex Teams notification webex_teams_apis.post_space_message( ROOM_NAME, 'Access to this device: IPD has been terminated') webex_teams_apis.post_space_message( ROOM_NAME, '----------------------------------------------') # update the database with script execution access_log_file = open('access_logs.csv', 'a') data_to_append = str('\n\n' + date_time) + ',' access_log_file.write(data_to_append) data_to_append = last_person_email + ',' + log_ipd_info + ',' + approver_email data_to_append += ',' + log_dc_info + ',' + log_remote_info + ',' + log_templ_depl_info + ',' data_to_append += log_path_trace + ',' + log_asav_info + ',\n' + snow_incident data_to_append_nolines = data_to_append.replace('\n\n', '\n') access_log_file.write(data_to_append_nolines) access_log_file.close() print('\nRecords database updated, file saved') comments = log_remove_info + log_asav_remove_info + log_ucsd_remove_info + log_sync_info service_now_apis.update_incident(snow_incident, comments, snow_user) # restore the stdout to initial value sys.stdout = initial_sys # the local date and time when the code will end execution date_time = str(datetime.datetime.now().replace(microsecond=0)) print('\n\nEnd of application "ERNA.py" run at this time ', date_time)
def main(): """ This script will load the file with the name {file_info} The file includes the information required to deploy the template. The network device hostname, the Cisco DNA Center project name, the configuration template file name. The application will: - verify if the project exists and create a new project if does not - update or upload the configuration template - commit the template - verify the device hostname is valid - deploy the template - verify completion and status of the template deployment :param template_info: the CLI command """ # the local date and time when the code will start execution date_time = str(datetime.datetime.now().replace(microsecond=0)) print('\n\nApplication "dnacenter_config_templates.py" Run Started: ' + date_time) # input data validation # open the file with the device, project and template info with open('data_input.txt', 'r') as f: data = f.read() template_info = json.loads(data) print('\nThe Cisco DNA Center Template information is: \n', template_info) device_hostname = template_info['device'] project_name = template_info['project'] template_file_name = template_info['template'] # get a Cisco DNA Center auth token dnac_auth = dnac_apis.get_dnac_jwt_token(DNAC_AUTH) # check if existing project, if not create a new project project_id = dnac_apis.create_project(project_name, dnac_auth) if project_id == 'none': # unable to find or create the project print('\nUnable to create the project: ', project_name) return # continue with the project id print('\nThe project id for the the project with the name "' + project_name + '" is: ' + project_id) # create new template and commit of not existing # update the existing template and commit if existing cli_config_name = template_file_name.split('.')[0] # select the template name from the template file cli_file = open(template_file_name, 'r') # open file with the template cli_config_commands = cli_file.read() # read the file # IPv4 conflict validation of the CLI template using the filename as input cli_valid = dnac_apis.check_ipv4_duplicate(template_file_name) # upload and commit the template dnac_apis.upload_template(cli_config_name, project_name, cli_config_commands, dnac_auth) if cli_valid is False: print('\n\nDeploying the CLI Template "' + cli_config_name + '" will not create duplicated IP Addresses') # deploy the template deployment_id = dnac_apis.send_deploy_template(cli_config_name, project_name, device_hostname, dnac_auth) print(deployment_id) time.sleep(1) deployment_status = dnac_apis.check_template_deployment_status(deployment_id, dnac_auth) print(deployment_status) else: print('\n\nDeploying the CLI Template "' + cli_config_name + '" will create duplicated IP Addresses') print('CLI Template not deployed') date_time = str(datetime.datetime.now().replace(microsecond=0)) print('\n\nEnd of Application "dnacenter_config_templates.py" Run: ' + date_time) return
def main(): """ Vendor will join Spark Room with the name {ROOM_NAME} It will ask for access to an IP-enabled device - named {IPD} The code will map this IP-enabled device to the IP address {172.16.41.55} Access will be provisioned to allow connectivity from DMZ VDI to IPD """ last_person_email = '*****@*****.**' timer = 2 # save the initial stdout initial_sys = sys.stdout # the user will be asked if interested to run in demo mode or in # production (logging to files - erna_log.log, erna_err.log)) user_input = utils.get_input_timeout( 'If running in Demo Mode please enter y ', 10) if user_input != 'y': # open a log file 'erna.log' file_log = open('erna_log.log', 'w') # open an error log file 'erna_err.log' err_log = open('erna_err.log', 'w') # redirect the stdout to file_log and err_log sys.stdout = file_log sys.stderr = err_log # configure basic logging to send to stdout, level DEBUG, include timestamps logging.basicConfig( level=logging.DEBUG, stream=sys.stdout, format=('%(asctime)s - %(levelname)s - %(message)s')) # the local date and time when the code will start execution date_time = str(datetime.datetime.now().replace(microsecond=0)) print('\nThe app started running at this time ' + date_time) user_input = 'y' user_input = utils.get_input_timeout('Enter y to skip next section : ', 10) if user_input != 'y': # verify if Spark Space exists, if not create Spark Space, and add membership (optional) spark_room_id = spark_apis.get_room_id(ROOM_NAME) if spark_room_id is None: spark_room_id = spark_apis.create_room(ROOM_NAME) print('- ', ROOM_NAME, ' - Spark room created') # invite membership to the room spark_apis.add_room_membership(spark_room_id, APPROVER_EMAIL) spark_apis.post_room_message(ROOM_NAME, 'To require access enter : IPD') spark_apis.post_room_message(ROOM_NAME, 'Ready for input!') print('Instructions posted in the room') else: print('- ', ROOM_NAME, ' - Existing Spark room found') spark_apis.post_room_message(ROOM_NAME, 'To require access enter : IPD') spark_apis.post_room_message(ROOM_NAME, 'Ready for input!') print('- ', ROOM_NAME, ' - Spark room id: ', spark_room_id) # check for messages to identify the last message posted and the user's email who posted the message # check for the length of time required for access last_message = (spark_apis.last_user_message(ROOM_NAME))[0] while last_message == 'Ready for input!': time.sleep(5) last_message = (spark_apis.last_user_message(ROOM_NAME))[0] if last_message == 'IPD': last_person_email = ( spark_apis.last_user_message(ROOM_NAME))[1] spark_apis.post_room_message( ROOM_NAME, 'How long time do you need access for? (in minutes) : ') time.sleep(10) if ( spark_apis.last_user_message(ROOM_NAME) )[0] == 'How long time do you need access for? (in minutes) : ': timer = 30 * 60 else: timer = int( spark_apis.last_user_message(ROOM_NAME)[0]) * 60 elif last_message != 'Ready for input!': spark_apis.post_room_message(ROOM_NAME, 'I do not understand you') spark_apis.post_room_message(ROOM_NAME, 'To require access enter : IPD') spark_apis.post_room_message(ROOM_NAME, 'Ready for input!') last_message = 'Ready for input!' print('\nThe user with this email: ', last_person_email, ' asked access to IPD for ', (timer / 60), ' minutes') # get the WJT Auth token to access DNA dnac_token = dnac_apis.get_dnac_jwt_token(DNAC_AUTH) print('\nThe DNA Center auth token is: ', dnac_token) # IPD IP address - DNS lookup if available IPD_IP = '10.93.140.35' # locate IPD in the environment using DNA C ipd_location_info = dnac_apis.locate_client_ip(IPD_IP, dnac_token) remote_device_hostname = ipd_location_info[ 0] # the network device the IPD is connected to vlan_number = ipd_location_info[2] # the access VLAN number interface_name = ipd_location_info[ 1] # the interface number is connected to device_location = dnac_apis.get_device_location( remote_device_hostname, dnac_token) # network device location location_list_info = device_location.split('/') remote_device_location = location_list_info[-1] # select the building name print('\nThe IPD is connected to:') print('this interface:', interface_name, ', access VLAN:', vlan_number) print('on this device:', remote_device_hostname) print('located: ', remote_device_location) # request approval if user_input != 'y': spark_apis.post_room_message( ROOM_NAME, ('The user with this email ' + last_person_email + ' asked access to IPD for ' + str(timer / 60) + ' minutes')) spark_apis.post_room_message( ROOM_NAME, 'The IPD is connected to the switch ' + remote_device_hostname + ' at our location ' + remote_device_location) spark_apis.post_room_message(ROOM_NAME, 'To approve enter: Y/N') # check for messages to identify the last message posted and the user's email who posted the message. # looking for user - Director email address, and message = 'Y' last_message = (spark_apis.last_user_message(ROOM_NAME))[0] while last_message == 'To approve enter: Y/N': time.sleep(5) last_message = (spark_apis.last_user_message(ROOM_NAME))[0] approver_email = (spark_apis.last_user_message(ROOM_NAME))[1] if last_message == 'y' or 'Y': if approver_email == APPROVER_EMAIL: print('Access Approved') else: last_message = 'To approve enter: Y/N' print('\nApproval process completed') # get UCSD API key # ucsd_key = get_ucsd_api_key() # execute UCSD workflow to connect VDI to VLAN, power on VDI # execute_ucsd_workflow(ucsd_key, UCSD_CONNECT_FLOW) print('UCSD connect flow executed') # deployment of interface configuration files to the DC router dc_device_hostname = 'PDX-RO' template_project = 'ERNA' print('\nThe DC device name is: ', dc_device_hostname) dc_int_config_file = 'DC_Interface_Config.txt' dc_int_templ = dc_int_config_file.split('.')[ 0] # select the template name from the template file cli_file = open(dc_int_config_file, 'r') # open file with the template cli_config = cli_file.read() # read the file # validation of dc router cli template valid = dnac_apis.check_ipv4_duplicate(dc_int_config_file) if not valid: print('\nDC Router CLI Templates validated') dc_temp_valid = True dnac_apis.upload_template(dc_int_templ, template_project, cli_config, dnac_token) # upload the template to DNA C depl_id_dc_int = dnac_apis.deploy_template(dc_int_templ, template_project, dc_device_hostname, dnac_token) # deploy time.sleep(1) # deployment of routing configuration files to the DC router dc_rout_config_file = 'DC_Routing_Config.txt' dc_rout_templ = dc_rout_config_file.split('.')[ 0] # select the template name from the template file cli_file = open(dc_rout_config_file, 'r') # open file with the template cli_config = cli_file.read() # read the file dnac_apis.upload_template(dc_rout_templ, template_project, cli_config, dnac_token) # upload the template to DNA C depl_id_dc_routing = dnac_apis.deploy_template(dc_rout_templ, template_project, dc_device_hostname, dnac_token) print('\nDeployment of the configurations to the DC Router, ', dc_device_hostname, 'started') time.sleep(1) # deployment of interface configuration files to the Remote router remote_int_config_file = 'Remote_Interface_Config.txt' remote_int_templ = remote_int_config_file.split('.')[ 0] # select the template name from the template file cli_file = open(remote_int_config_file, 'r') # open file with the template cli_config = cli_file.read() # read the file dnac_apis.upload_template(remote_int_templ, template_project, cli_config, dnac_token) # upload the template to DNA C depl_id_remote_int = dnac_apis.deploy_template(remote_int_templ, template_project, remote_device_hostname, dnac_token) # deploy time.sleep(1) # deployment of routing configuration files to the Remote router remote_rout_config_file = 'Remote_Routing_Config.txt' remote_rout_templ = remote_rout_config_file.split('.')[ 0] # select the template name from the template file cli_file = open(remote_rout_config_file, 'r') # open file with the template cli_config = cli_file.read() # read the file # update the template with the localized info for the IPD # replace the $VlanId with the localized VLAN access # replace the $IPD with the IPD ip address cli_config = cli_config.replace('$IPD', IPD_IP) cli_config = cli_config.replace('$VlanId', vlan_number) # validation of remote router cli template valid = dnac_apis.check_ipv4_duplicate(cli_config) if not valid: print('\nRemote Device CLI Templates validated') remote_templ_valid = True dnac_apis.upload_template(remote_rout_templ, template_project, cli_config, dnac_token) # upload the template to DNA C depl_id_remote_routing = dnac_apis.deploy_template(remote_rout_templ, template_project, remote_device_hostname, dnac_token) # deploy print('\nDeployment of the configurations to the Remote device, ', remote_device_hostname, ' started') time.sleep(1) # check the deployment status after waiting for all jobs to complete - 5 seconds print('\nWait for DNA Center to complete template deployments') time.sleep(10) dc_interface_status = dnac_apis.check_template_deployment_status( depl_id_dc_int, dnac_token) dc_routing_status = dnac_apis.check_template_deployment_status( depl_id_dc_routing, dnac_token) remote_interface_status = dnac_apis.check_template_deployment_status( depl_id_remote_int, dnac_token) remote_routing_status = dnac_apis.check_template_deployment_status( depl_id_remote_routing, dnac_token) print('Templates deployment status: ', dc_interface_status, dc_routing_status, remote_interface_status, remote_routing_status) if dc_interface_status == 'SUCCESS' and dc_routing_status == 'SUCCESS' and remote_interface_status == 'SUCCESS' and remote_routing_status == 'SUCCESS': print('\nAll templates deployment have been successful\n') templ_deploy_status = True # synchronization of devices configured - DC and Remote Router dc_sync_status = dnac_apis.sync_device(dc_device_hostname, dnac_token)[0] remote_sync_status = dnac_apis.sync_device(remote_device_hostname, dnac_token)[0] if dc_sync_status == 202: print('\nDNA Center started the DC Router resync') if remote_sync_status == 202: print('\nDNA Center started the Remote Router resync') dc_router_tunnel = netconf_restconf.get_restconf_int_oper_status( 'Tunnel201') remote_router_tunnel = netconf_restconf.get_netconf_int_oper_status( 'Tunnel201') print('\nThe Tunnel 201 interfaces operational state:') print('From ', remote_device_hostname, ' using NETCONF -', dc_router_tunnel) print('From ', dc_device_hostname, ' using RESTCONF -', remote_router_tunnel) print('\nWait for DNA Center to complete the resync of the two devices') time.sleep(240) # start a path trace to check the path segmentation path_trace_id = dnac_apis.create_path_trace('172.16.202.1', IPD_IP, dnac_token) print('\nWait for Path Trace to complete') time.sleep(20) path_trace_info = dnac_apis.get_path_trace_info(path_trace_id, dnac_token) print('\nPath Trace status: ', path_trace_info[0]) print('\nPath Trace details: ', path_trace_info[1]) # create ASAv outside interface ACL to allow traffic outside_acl_id = asav_apis.get_asav_access_list(OUTSIDE_INT) asav_status = asav_apis.create_asav_access_list(outside_acl_id, OUTSIDE_INT, VDI_IP, IPD_IP) if asav_status == 201: print('ASAv access list updated to allow traffic from ', VDI_IP, ' to ', VDI_IP, ' on the interface ', OUTSIDE_INT) else: print('Error updating the ASAv access list on the interface ', OUTSIDE_INT) # Spark notification spark_apis.post_room_message( ROOM_NAME, 'Requested access to this device: IPD, located in our office ' + remote_device_location + ' by user ' + last_person_email + ' has been granted for ' + str(int(timer / 60)) + ' minutes') # Tropo notification - voice call voice_notification_result = spark_apis.tropo_notification() spark_apis.post_room_message( ROOM_NAME, 'Tropo Voice Notification: ' + voice_notification_result) # time.sleep(timer) input('\nInput any key to continue ! ') # # restore configurations to initial state # # restore DC router config dc_del_file = 'DC_Delete.txt' dc_del_templ = dc_del_file.split('.')[0] cli_file = open(dc_del_file, 'r') cli_config = cli_file.read() dnac_apis.upload_template(dc_del_templ, template_project, cli_config, dnac_token) depl_id_dc_del = dnac_apis.deploy_template(dc_del_templ, template_project, dc_device_hostname, dnac_token) print('\nDC Router restored to the baseline configuration') time.sleep(1) # restore Remote router config remote_del_file = 'Remote_Delete.txt' remote_del_templ = remote_del_file.split('.')[0] cli_file = open(remote_del_file, 'r') cli_config = cli_file.read() # update the template with the local info for the IPD # replace the $VlanId with the local VLAN access # replace the $IPD with the IPD ip address cli_config = cli_config.replace('$IPD', IPD_IP) cli_config = cli_config.replace('$VlanId', vlan_number) dnac_apis.upload_template(remote_del_templ, template_project, cli_config, dnac_token) print('\nRemote Router restored to the baseline configuration') time.sleep(1) # remove the ASAv outside interface ACLE that allowed traffic between VDI and IPD outside_acl_id = asav_apis.get_asav_access_list(OUTSIDE_INT) asav_status = asav_apis.delete_asav_access_list(outside_acl_id, OUTSIDE_INT) if asav_status == 204: print('\nASAv access list on the interface ', OUTSIDE_INT, ' restored to the baseline configuration') else: print('Error while restoring the ASAv access list on the interface ', OUTSIDE_INT) # execute UCSD workflow to discoconnect VDI to VLAN, power on VDI # execute_ucsd_workflow(ucsd_key, UCSD_DISCONNECT_FLOW) print('\nUCSD disconnect flow executed') # Spark notification spark_apis.post_room_message( ROOM_NAME, 'Access to this device: IPD has been terminated') # update the database with script execution access_log_file = open('access_logs.csv', 'a') data_to_append = [ date_time, last_person_email, IPD_IP, approver_email, dc_temp_valid, remote_templ_valid, templ_deploy_status, dc_router_tunnel, remote_router_tunnel, path_trace_info ] access_log_file.write(data_to_append) access_log_file.close() print('\nRecords database updated, file saved') # restore the stdout to initial value sys.stdout = initial_sys # the local date and time when the code will end execution date_time = str(datetime.datetime.now().replace(microsecond=0)) print('\n\nEnd of application run at this time ', date_time)