Programatically Enable NETCONF and MD-CLI on Nokia – SROS Using Netbox API. (Part 2)

CODE: https://github.com/h4ndzdatm0ld/sros-enable-netconf/blob/master/enable-netconf-netboxapi.py

Okay, so you’re still reading? Lets keep digging into the mind of the network team and see how this exercisce is going..  So far, we’ve got a command line tool that we can target one specific node and deploy a script to enable MD-CLI, Yang Models from Nokia and of course, NETCONF. But, how far does this really get us? Really, it’s usefull to test on a handful of nodes and see the behaviour and response to our scripts in a lab environemnt. I’ve tested it on several SROS 19.10R3 A8’s and SR1’s.

It’s time to elaborate on the script and use the tool the Avifi has already deployed and mainted, Netbox. If you haven’t heard of Netbox, google it. In short it’s an IPAM/DCIM and so much more. The customer has requested we do not make any changes to anything in the subnet 10.0.0.0/16, anything else is fair game.  Lucky for us, the IP’s we need have been tagged with ‘7750’, would you believe that?! We’ll use a filter on our API call to extract the IP’s that we need and loop through them doing, but also leaving out anything in the 10xspace. We’ve taken a step back from the command line driven tool model and make a few things a bit more static, by using default arguements from the argparse package.

Before writing any more code, lets pull an API token from the Netbox server.

Here are the instructions: https://netbox.readthedocs.io/en/stable/api/authentication/

We’ll put this token into our application.. not the most secure way of doing this, but for simplicity – we’ll store it in a var in plain text for now. In my opinion, the authorization handled by the netbox administrator should theoretically prevent us from doing anything catastrophic when providing a user with an API token. .. in a perfect world 😉

Lets get to coding! Screen Shot 2020-05-19 at 1.16.50 PM

I thought about passing the argsparsge args into this function and having the ability to pass in an arguement as a ‘tag’ to filter by on the API call, but I didn’t think that was necessary. Although it could be usefull later and a quick and easy modification.

The code above shows nb as the pynetbox authenticated api requests. We then use the application form ‘ipam.ip_addresses‘ and filter by a tag, in which we pass in as an arguement on the function.

The customer requested we skip over any device in the RFC 10 Space, so we create a conditional statement to evaluate the IP’s. Note this is a very broad catch, it should be redefined if this were production as 192, could very much contain ’10.’. I would recommend adding the startswith() function and be more specific. But for now, this works.

 


for x in SR7750:
ip = get_ip_only(x)
if '10' in ip:
print(f'skipping {ip} – We do not want to edit nodes in this subnet.')
elif '192' in ip:
sros_conn = net_connect = ConnectHandler(**router_dict(args,ip,SSH_PASS))
# Establish a list of pre and post check commands.
print('Connecting to device and executing script…')
send_single(sros_conn, 'show system information | match Name')
enabled = sros_conn.send_command('show system netconf | match State')
if 'Enabled' in enabled:
print(f"{ip} already has NETCONF enabled. Moving on..")
disconnect(sros_conn)
time.sleep(2)
print('\n')
try:
netcbackup(ip, NETCONF_USER, NETCONF_PASS)
except Exception as e:
print(f"{e}")
continue

view raw

condiitional ip

hosted with ❤ by GitHub

 

We loop through the IP results in which we got back from the API call and strip the subnet mask using regular expressions. We than pass the IP into our Netconf connection and proceed to get the configuration. Here is a snippet of the RegEX function to strip the /subnet mask from the IP.


def get_ip_only(ipadd):
''' This function will use REGEX to strip the subnet mask from an IP/MASK addy.
'''
try:
ip = re.sub(r'/.+', '', str(ipadd))
return ip
except Exception as e:
print(f"Issue striping subnetmask from {ipadd}, {e}")

view raw

get_ip

hosted with ❤ by GitHub

Finally, I created a function that will establish the initial netcon connection and get.config. We save the netconf element to a file and open it to be able to parse the xml contents, with xmltodict. With this, we extract the system host name and use it as a var to create a folder directory and a file name. We save the running configuration in xml format.


def netcbackup(ip, NETCONF_USER, NETCONF_PASS):
''' This function will establish a netconf connection and pull the running config. It will write a temp file,
read it and convert the XML to a python dictionary. Once parsed, we'll pull the system name of the device
and create a folder structure by hostname and backup the running config.
'''
try:
# Now let's connect to the device via NETCONF and pull the config to validate
nc = netconfconn(ip, NETCONF_USER, NETCONF_PASS)
# Grab the running configuration on our device, as an NCElement.
config = nc.get_config(source='running')
# XML elemnent as a str.
xmlconfig = to_xml(config.xpath('data')[0])
# Write the running configuration to a temp-file (from the data/configure xpath).
saveFile('temp-config.xml', xmlconfig)
# Lets open the XML file, read it, and convert to a python dictionary and extract some info.
with open('temp-config.xml', 'r') as temp:
content = temp.read()
xml = xmltodict.parse(content)
sys_name = xml['data']['configure']['system']['name']
createFolder(f"Configs/{sys_name}")
saveFile(
f"Configs/{sys_name}/{sys_name}.txt", xmlconfig)
except Exception as e:
print(e)

view raw

netconfbackup

hosted with ❤ by GitHub

Programatically Enable NETCONF and MD-CLI on Nokia – SROS

Hi everyone,

First things first, the code lives here:

https://github.com/h4ndzdatm0ld/sros-enable-netconf

I wanted to put together a mini-series of posts on how to programatically enable netconf across many ALU/Nokia – SROS devices.

The theoretical problem we are trying to solve:

  • Company Avifi has recently decided to enable NETCONF across their entire 7750 platform. They would like to do this all in one maintenance night.
  • All of Avifi’s network is currently documented and stored in Netbox. We must extract a list of 7750’s and their IP addresses using the API requests.
  • Programatically SSH into all the necessary devices:
    • Enable NETCONF
    • Create a NETCONF USER/PASSWORD
    • Enable Model-Driven CLI.

As a network engineer that’s constantly having to re-use scripts, templates, etc – I’d see this as an opportunity to create two things:

  1. A tool I can easily use in my lab environment before I take this to production.
  2. A production ready tool that my team can use.

We’ll start with a command line driven tool to easily target a single node, ssh into it and programatically enable NETCONF as well as change from the standard CLI to the new Model-Driven CLI that Nokia offers on their 7750’s routers.

As I’m getting more into the Dev/NET/OPS side of the house, I’m starting to think about CI/CD, unit tests, version control and the extensive amount of testing and variables that may change when implementing network wide changes via automation.

Let’s discuss some of the packages i’ll be using with Python 3.

Everyone should be familiar with Netmiko by now. We’ll use this to connect via SSH to our devices and manipulate our configurations.  As the starting point to this will be to build from a command line driven utility which targets a single node and expand into extracting a list of devices via Netbox, we will use argparse to send arguements from the CLI to our python script. NCCLIENT will be used to establish NETCONF connections. In order to not store passwords on our script, we will use getpass to prompt our users for passwords. On our future updated post, we’ll call the pynetbox package / API client to interact with Netbox and extract the correct device IP addresses and run the script against it. xmltodict to convert the xml extracted file and parse to a dcitionary.

Screen Shot 2020-05-18 at 12.07.21 PM

The tool will accept the arguements above, but the SSH username is defaulted to ‘admin’.

Once ran, the script will request for the SSH Password to the device, it will connect and send a list of commands to enable the NETCONF service and also switch from the Classic CLI to the new Model Driven CLI. Once this is complete, the SSH connection will be dropped and a new connection on port 830, the default NETCONF port will be established utilizing the new credentials. The tool will proceed to extract the running configuration, it will save a temp file and re-open it to parse it into a dictionary. We’ll extract the system name and use it as a var to create a folder directory of configurations and save the XML configuration by system name.

Before running, open the script and edit the new usser credentials that you wish to pass for NETCONF connections. 

At this point, i’m able to run this against a multitude of devices individually to test functionallity and make any adjustments before I implement the API connection into our Netbox server.

Below is the entire code, at beta. This command line driven utility will utilize NETMIKO to establish the initial connection to the device.  On the next post, we will take this code and change quite a bit to dynamically pass in a list of hosts from the NETBOX API.

import netmiko, ncclient, argparse, getpass, sys, time, xmltodict, os
from netmiko import ConnectHandler
from ncclient import manager
from ncclient.xml_ import *
from xml.etree import ElementTree
def get_arguments():
parser = argparse.ArgumentParser(description='Command Line Driven Utility To Enable NETCONF\
On SROS Devices And MD-CLI.')
parser.add_argument("-n", "--node", help="Target NODE IP", required=True)
parser.add_argument("-u", "--user", help="SSH Username", required=False, default='admin')
parser.add_argument("-p", "--port", help="NETCONF TCP Port", required=False, default='830')
args = parser.parse_args()
return args
# Lets make it easier to send and receive the output to the screen.
# We'll create a function to pass in a list of commands as arguements.
def send_cmmdz(node_conn,list_of_cmds):
''' This function will unpack the dictionary created for the remote host to establish a connection with
and send a LIST of commands. The output will be printed to the screen.
Establish the 'node_conn' var first by unpacking the device connection dictionary. Pass it in as an args.
'''
try:
x = node_conn.send_config_set(list_of_cmds)
print(x)
exceptExceptionas e:
print(f"Issue with list of cmdz, {e}")
def send_single(node_conn, command):
''' This function will unpack the dictionary created for the remote host to establish a connection with
and send a single command. The output will be printed to the screen.
Establish the 'node_conn' var first by unpacking the device connection dictionary. Pass it in as an args.[]
'''
try:
x = node_conn.send_command(command)
print (x)
exceptExceptionas e:
sys.exit(e)
def disconnect(node_conn):
try:
node_conn.disconnect()
exceptExceptionas e:
print(e)
def netconfconn(args,ncusername,ncpassword):
conn = manager.connect(host=args.node,
port=args.port,
username=ncusername,
password=ncpassword,
hostkey_verify=False,
device_params={'name':'alu'})
return conn
def saveFile(filename, contents):
''' Save the contents to a file in the PWD.
'''
try:
f = open(filename, 'w+')
f.write(contents)
f.close()
exceptExceptionas e:
print(e)
def createFolder(directory):
try:
ifnot os.path.exists(directory):
os.makedirs(directory)
exceptOSError:
print('Error: Creating directory. '+ directory)
def main():
# Extract the Arguements from ARGSPARSE:
args = get_arguments()
# Define the NETCONF USERNAME / PASSWORD:
NETCONF_USER = 'netconf'
NETCONF_PASS = 'NCadmin123'
# # Create a dictionary for our device.
sros = {
'device_type': 'alcatel_sros',
'host': args.node,
'username': args.user,
'password': getpass.getpass(),
}
# Pass in the dict and create the connection.
sros_conn = net_connect = ConnectHandler(**sros)
# Establish a list of pre and post check commands.
print('Connecting to device and executing script...')
send_single(sros_conn, 'show system information | match Name')
send_single(sros_conn, 'show system netconf | match State')
enableNetconf = ['system security profile "netconf" netconf base-op-authorization lock',
'system security profile "netconf" netconf base-op-authorization kill-session',
f'system security user {NETCONF_USER} access netconf',
f'system security user {NETCONF_USER} password {NETCONF_PASS}',
f'system security user {NETCONF_USER} console member {NETCONF_USER}',
f'system security user {NETCONF_USER} console member "administrative"',
'system management-interface yang-modules nokia-modules',
'system management-interface yang-modules no base-r13-modules',
'system netconf auto-config-save',
'system netconf no shutdown',
'system management-interface cli md-cli auto-config-save',
'system management-interface configuration-mode model-driven']
# Execute Script.
send_cmmdz(sros_conn, enableNetconf)
# Validate NETCONF is enabled and Operational.
send_single(sros_conn,'show system netconf')
# Disconnect from the SSH Connection to our far-end remote device.
# We need to disconnect to open the pipe for python3 to establish netconf connection.
disconnect(sros_conn)
time.sleep(2)
try:
# Now let's connect to the device via NETCONF and pull the config to validate.
nc = netconfconn(args, NETCONF_USER, NETCONF_PASS)
# Grab the running configuration on our device, as an NCElement.
config = nc.get_config(source='running')
# XML elemnent as a str.
xmlconfig = to_xml(config.xpath('data')[0])
# Write the running configuration to a temp-file (from the data/configure xpath).
saveFile('temp-config.xml', xmlconfig)
# Lets open the XML file, read it, and convert to a python dictionary and extract some info.
withopen('temp-config.xml', 'r') as temp:
content = temp.read()
xml = xmltodict.parse(content)
sys_name = xml['data']['configure']['system']['name']
createFolder('Configs')
saveFile(f"Configs/{sys_name}.txt", xmlconfig)
exceptExceptionas e:
print(f"Issue with NETCONF connection, {e}")
if __name__ == "__main__":
main()