Three python scripts were written for doing specific tasks.
- conn_asa.py – this script is responsible for generating two outputs hostname and md_now (md is short for message digest)
- statechange.py – this script writes the hash generated from conn_asa.py into database.
- compare_change.py – this script compares the hash stored in db with the current hash generated from conn_asa.py
conn_asa.py
The script uses netmiko to send show run object network
and show hostname
.
The result from show run object network
is hashed with sha256, the digest string is passed into md_now variable.
The result from show hostname
returns the hostname\n
, the split() method is used to remove the trailing newline if this is not removed the entire hostname with the newline will be stored in database causing the where
clause executed by SQLAlchemy to become false causing SQLAlchemy to return no result.
from netmiko import ConnectHandler import hashlib # testing dictionary, do not hardcode credential into code. asa = { 'device_type': 'cisco_asa', 'ip': '192.168.1.14', 'username': 'cisco', 'password': 'cisco' } connect_asa = ConnectHandler(**asa) results = connect_asa.send_command('show run object network') # split() has to be used to remove the newline \n return by ConnectHandler # if split() or rsplit() is not used the trailing \n will fail the where clause. hostname = connect_asa.send_command('show hostname').split() # create a sha256 object, sha256 has more reliability than sha1. sha = hashlib.sha256() # hash the result from show run object network, # has to convert into unicode byte before hashing sha.update(results.encode('utf-8')) # pass the hash string into md_now object. md_now = sha.hexdigest()
statechange.py
from os.path import exists from sqlalchemy import TEXT, Integer, Table, create_engine, Column, MetaData from conn_asa import md_now, hostname db = create_engine('sqlite:///state.db') metadata = MetaData(db) if not exists('state.db'): network_object_state = Table('network_object_state', metadata, Column('state_id', Integer, primary_key=True), Column('hostname', TEXT, nullable=False), Column('message_digest', TEXT, nullable=False)) network_object_state.create() else: network_object_state = Table('network_object_state', metadata, autoload=True) i = network_object_state.insert() i.execute({'hostname': hostname[0], # split() was used to return hostname, hence hostname becomes a list. 'message_digest': md_now}) db.dispose()
compare_change.py
from sqlalchemy import Table, MetaData, create_engine from conn_asa import md_now db = create_engine('sqlite:///state.db') metadata = MetaData(db) network_object_state = Table('network_object_state', metadata, autoload=True) md_then = network_object_state.select(network_object_state.c.hostname == 'ciscoasa').execute() db.dispose() # md_then cannot be returned without using fetchall or fetchone, md_then is just a SQL object. # fetchone returns one row from the database, each row is a tuple. # fetchall returns more than one row if the condition matches, the result will be a list of tuples. print("Network Objects unchanged" if md_then.fetchone()[2] == md_now else "Network Objects have changed")
The code assumes the hostnames are unique for all devices, if there are same hostnames then the code will not work correctly, the fetchone
returns the row that SQL first found with the SQL condition.