python_vpn_audit/vpn_audit/vpn_scrapli.py

223 lines
9.7 KiB
Python

from dotenv import load_dotenv
import os
from scrapli import Scrapli
from scrapli.driver import GenericDriver
# from scrapli.driver.core import IOSXEDriver
import logging
from logging import handlers
from bson.json_util import dumps, loads
# accept dict of commands and associated functions to parse command output, run commands directly and pass output to parsing function or pass connection dict (compound) to parsing function to run multiple commands
def device_commands(collection, object_id, commands):
## init
load_dotenv()
sshuser = os.environ.get('SSH_USER')
sshpass = os.environ.get('SSH_PASSWORD')
query = {"_id": object_id}
result = collection.find(query)
device_record = result[0]
ip = device_record['IPAddress']
device_name = device_record['DeviceName']
scrapli_platform = device_record['scrapli_platform']
## log
logger = logging.getLogger(device_name)
logger.info(f'Send commands to device - {device_name} - {ip} - {scrapli_platform}')
## send commands
# timeout = 60 # default
timeout = 120 # australia
connection = {
"host": ip,
"auth_username": sshuser,
"auth_password": sshpass,
"auth_secondary": sshpass,
"auth_strict_key": False,
"ssh_config_file": "/etc/ssh/ssh_config",
"platform": scrapli_platform,
"timeout_socket": timeout,
"timeout_transport": timeout,
"timeout_ops": timeout,
}
device_commands = commands.copy()
scrapli_commands_keys = [c for c in device_commands.keys() if not device_commands[c]['command'] == 'compound']
try:
with Scrapli(**connection) as conn:
# send commands over a single socket to avoid session-limits/IDS
for k in scrapli_commands_keys:
command = device_commands[k]['command']
# print(f"sending command '{command}' for {k}")
logger.info(f"Sending command '{command}' for {k}")
output = conn.send_command(command)
device_commands[k].update({'output': output})
except Exception as e:
# print(f'exception_type: {type(e).__name__}')
logger.error(f"Exception occurred: {type(e).__name__}", exc_info=True)
return f'{device_name} error: scrapli failure {command}'
## run scrape processors
for c in device_commands.keys():
func = commands[c]['func_ref']
command_output = commands[c]
func(collection, command_output, device_record, connection)
## success end
return 'processed'
# accept dict of commands and associated functions to parse command output, run commands directly and pass output to parsing function or pass connection dict (compound) to parsing function to run multiple commands
def device_commandsAA(collection, object_ids, commands):
# logger = logging.getLogger('main')
load_dotenv()
# error_encountered = False
sshuser = os.environ.get('SSH_USER')
sshpass = os.environ.get('SSH_PASSWORD')
query = { "_id" : { "$in" : object_ids } }
result = collection.find(query)
for device_record in result:
ip = device_record['IPAddress']
device_name = device_record['DeviceName']
scrapli_platform = device_record['scrapli_platform']
# print(f"\nquery device - {device_name} - {ip} - {scrapli_platform}")
local_logger = logging.getLogger(device_name)
local_logger.info(f'Send commands to device - {device_name} - {ip} - {scrapli_platform}')
# timeout = 60 # default
# timeout = 120 # australia
timeout = 180 # RRI in australia/malaysia
connection = {
"host": ip,
"auth_username": sshuser,
"auth_password": sshpass,
"auth_secondary": sshpass,
"auth_strict_key": False,
"ssh_config_file": "/etc/ssh/ssh_config",
"platform": scrapli_platform,
"timeout_socket": timeout,
"timeout_transport": timeout,
"timeout_ops": timeout,
}
device_commands = commands.copy()
scrapli_commands_keys = [c for c in device_commands.keys() if not device_commands[c]['command'] == 'compound']
try:
with Scrapli(**connection) as conn:
# send commands over a single socket to avoid session-limits/IDS
for k in scrapli_commands_keys:
command = device_commands[k]['command']
# print(f"sending command '{command}' for {k}")
local_logger.info(f"Sending command '{command}' for {k}")
output = conn.send_command(command)
device_commands[k].update({'output': output})
except Exception as e:
# print(f'exception_type: {type(e).__name__}')
# return f'{device_name} error: scrapli failure'
local_logger.error(f"Exception occurred: {type(e).__name__}", exc_info=True)
return f'{device_name} error: scrapli failure {command}'
# update all commands that didnt run with error status (should not get to this point)
# for k in scrapli_commands_keys:
# if 'output' not in device_commands[k]:
# device_commands[k]['output'] = 'error'
# error_encountered = True
# if error_encountered:
# return f'{device_name} error: scrapli failure'
# run scrape processors
for c in device_commands.keys():
func = commands[c]['func_ref']
command_output = commands[c]
func(collection, command_output, device_record, connection)
return 'processed'
def identify_scrapli_platform(osinfo):
# parse output of 'generic' device type command 'show version' for cisco/junos, will use this to find netmiko ConnectHandler 'device_type' parameter / scrapli_platform
# Cisco IOS Software, C3900 Software (C3900-UNIVERSALK9-M), Version 15.4(3)M2, RELEASE SOFTWARE (fc2) # first line ios, scrapli platform cisco_iosxe
# Cisco IOS XE Software, Version 03.16.06.S - Extended Support Release # first line iosxe, scrapli platform cisco_iosxe
# JUNOS Software Release [12.1X44-D30.4] # 2nd line junos, scrapli platform juniper_junos
vendors = ['cisco', 'junos']
cisco = [{'os': 'Cisco IOS Software', 'scrapli_platform': 'cisco_iosxe', 'line': 0},
{'os': 'Cisco IOS XE Software', 'scrapli_platform': 'cisco_iosxe', 'line': 0}]
junos = [{'os': 'JUNOS Software Release', 'scrapli_platform': 'juniper_junos', 'line': 2}]
for v in vendors:
if v in osinfo.lower():
vendor = v
if not 'vendor' in locals():
vendor = 'unknown'
match vendor:
case "cisco":
# known cisco os
for i in cisco:
scrape_line = osinfo.partition('\n')[i['line']]
if i['os'] in scrape_line:
scrapli_platform = i['scrapli_platform']
record = {'scrapli_platform': scrapli_platform, 'vendor': vendor }
# print(record)
# unknown cisco os
if not 'scrapli_platform' in locals():
scrapli_platform = 'generic'
record = {'scrapli_platform': scrapli_platform, 'vendor': vendor }
# print(record)
case "junos":
# known junos os
for i in junos:
scrape_line = osinfo.partition('\n')[i['line']]
if i['os'] in scrape_line:
scrapli_platform = i['scrapli_platform']
record = {'scrapli_platform': scrapli_platform, 'vendor': vendor }
# print(record)
# catch all
case _:
scrapli_platform = 'generic'
record = {'scrapli_platform': scrapli_platform, 'vendor': vendor }
# print(record)
return record
def get_os(collection, object_ids):
# print('\nget_os')
logger = logging.getLogger('main')
logger.info('Get device OS type, update device records with Scrapli driver')
load_dotenv()
sshuser = os.environ.get('SSH_USER')
sshpass = os.environ.get('SSH_PASSWORD')
query = { "_id" : { "$in" : object_ids } }
result = collection.find(query)
for i in result:
name = i['DeviceName']
ip = i['IPAddress']
id = i['_id']
if not ip == 'unknown':
device = {
"host": ip,
"auth_username": sshuser,
"auth_password": sshpass,
"auth_strict_key": False,
"ssh_config_file": "/etc/ssh/ssh_config",
"timeout_socket": 15,
"timeout_transport": 15,
"timeout_ops": 15,
}
# use generic driver to help identify *any* OS to then select vendor specific drivers
# may require multiple try statements for commands that identify different OS vendors, show version may not be present on a VA/Sarian?
try:
with GenericDriver(**device) as conn:
# print(conn.ssh_config_file) # modern OS disable legacy KEX/Cipers/etc - the systemwide ssh_config has been compromised to use legacy ciphers, in future provide local ssh_config
conn.send_command("terminal length 0")
response = conn.send_command("show version")
sshresult = True
osinfo = identify_scrapli_platform(response.result)
except Exception as e:
# print(f'{name} connection error, exception_type {type(e).__name__}')
logger.error(f'{name} connection error, exception_type {type(e).__name__}')
sshresult = False
session_message = 'unknown'
if type(e).__name__ == 'ScrapliAuthenticationFailed':
session_message = 'ssh failed auth'
if type(e).__name__ == 'ScrapliTimeout':
session_message = 'ssh failed connection'
if sshresult:
osinfo.update({'session_protocol': 'ssh'})
filter = {'_id': id}
record = osinfo
update = collection.update_one(filter, {'$set': record}, upsert=True)
else:
filter = {'_id': id}
record = {'session_protocol': session_message}
update = collection.update_one(filter, {'$set': record}, upsert=True)