NX-API Part 2

In my previous post (NX-API Part 1) I provided a quick overview of the NX-API sandbox and a pointer to some documentation on Cisco.  There are a few very important concerns that we need to address before we can truly start using the NX-API.  Below is the breakdown of problem/resolutions I encountered while trying to address each concern:

SSL Versioning

First, you might have noticed the example url is using http with basic authentication.  It would be nice if this data was encrypted via https, especially since we’re sending the credentials in clear text in these requests!  If you’re like me, you’d think this would be as easy as changing the url from http to https and we’d be done.  Unfortunately we get a nasty traceback after making that change:

Traceback (most recent call last):
 File "/Library/Python/2.7/site-packages/requests/sessions.py", line 469, in get
 return self.request('GET', url, **kwargs)
 File "/Library/Python/2.7/site-packages/requests/sessions.py", line 457, in request
 resp = self.send(prep, **send_kwargs)
 File "/Library/Python/2.7/site-packages/requests/sessions.py", line 569, in send
 r = adapter.send(request, **kwargs)
 File "/Library/Python/2.7/site-packages/requests/adapters.py", line 420, in send
 raise SSLError(e, request=request)
requests.exceptions.SSLError: [Errno 8] _ssl.c:507: EOF occurred in violation of protocol

SSLError?!?  Turns out that Cisco’s NX-API uses SSLv3 which is incompatible with the default SSL version used in the urllib3 library (at least for me on python 2.7.6).  Fortunately we can configure which version of SSL (or TLS) to use in our requests by creating a custom SSL adaptor.  Here’s an awesome workaround published by  @Lukasaoz:

class SSLAdapter(HTTPAdapter):
   """
   Post by @Lukasaoz
  https://github.com/Lukasa/blog-posts/blob/master/posts/Choosing_SSL_Version_In_Requests.md
   """
   def __init__(self, ssl_version=None, **kwargs):
   # verify appropriate SSL version or default to SSLv23
   self.ssl_version = {
     ssl.PROTOCOL_SSLv23: ssl.PROTOCOL_SSLv23,
     ssl.PROTOCOL_SSLv2: ssl.PROTOCOL_SSLv2,
     ssl.PROTOCOL_SSLv3: ssl.PROTOCOL_SSLv3,
     ssl.PROTOCOL_TLSv1: ssl.PROTOCOL_TLSv1
     }.get(ssl_version, ssl.PROTOCOL_SSLv23)
   super(SSLAdapter, self).__init__(**kwargs)

   def init_poolmanager(self, connections, maxsize, block=False):
     self.poolmanager = PoolManager(num_pools=connections,
       maxsize=maxsize,
       block=block,
       ssl_version=self.ssl_version)

With the adapter we can make a small change to our requests and specifically call out SSLv3:

s = requests.Session()
s.mount(url, SSLAdapter(ssl.PROTOCOL_SSLv3))
response = s.post(url,data=json.dumps(payload), headers=myheaders,auth=(switchuser,switchpassword)).json()

Self-Signed Cerfiticate

Issue fixed?  No, we’ve fixed the SSL error but have a new traceback:

 File "/Library/Python/2.7/site-packages/requests/sessions.py", line 500, in post
 return self.request('POST', url, data=data, json=json, **kwargs)
 File "/Library/Python/2.7/site-packages/requests/sessions.py", line 457, in request
 resp = self.send(prep, **send_kwargs)
 File "/Library/Python/2.7/site-packages/requests/sessions.py", line 569, in send
 r = adapter.send(request, **kwargs)
 File "/Library/Python/2.7/site-packages/requests/adapters.py", line 420, in send
 raise SSLError(e, request=request)
requests.exceptions.SSLError: [Errno 1] _ssl.c:507: error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed

Certificate verify failed.  Well, it looks like the Nexus device is using a self-signed certificate that can’t be verified.  At this point, I don’t really care to install a valid certificate on my network device (although this can be done, check here), I just want an encrypted HTTP session between it and my script.

The python requests library provides a verify flag that can be disabled in any request to allow connections to devices with self-signed certificates:

s = requests.Session()
s.mount(url, SSLAdapter(ssl.PROTOCOL_SSLv3))
response = s.post(url, verify=False, data=json.dumps(payload), headers=myheaders,auth=(switchuser,switchpassword)).json()

At this point we can successfully and securely send and receive data via the NX-API.

Session Handling (Cookies)

The last step is to add session handling to our script.  Why?  Because it reduces the load on the network device:

“NX-API provides a session-based cookie, nxapi_auth when users first successfully authenticate. With the session cookie, the username and password are included in all subsequent NX-API requests that are sent to the device. The username and password are used with the session cookie to bypass performing the full authentication process again. If the session cookie is not included with subsequent requests, another session cookie is required and is provided by the authentication process. Avoiding unnecessary use of the authentication process helps to reduce the workload on the device.“
Cisco N9K NX-API Documentation

Creating and maintaining sessions is not too difficult with the requests library.  Basically, we need to perform a post providing basic authentication (using the https connection we created above), and save the cookie that the NX-API creates.  Then, we supply the cookie in subsequent requests.  For example:

s = requests.Session()
s.mount(url, SSLAdapter(ssl.PROTOCOL_SSLv3))
response = s.post(url,data=json.dumps(payload), headers=myheaders,auth=(switchuser,switchpassword), verify=False)
cookie = requests.utils.dict_from_cookiejar(response.cookies)

# later requests, leave out authentication and just supply cookie
response = s.post(url,data=json.dumps(payload), headers=myheaders, cookies=cookie, verify=False)

Final Product

I prefer to write once and reuse many, so if we can abstract the above concepts into a class and never look at them again, that would be ideal!  I created an NXAPIClient class that allows us to provide hostname and credentials, and then simply call the cli_show (as well as cli_show_ascii, cli_conf, or bash) function.  The code can be found on github: https://github.com/agccie/nxapi/

Here’s an example file utilizing the NX-API client:

#!/usr/bin/python

from nxapi_client import *
nxapi = NXAPIClient(hostname="clt-n9ka", username="varrow", password="ILoveVarrow!")
print nxapi.cli_show("show version")
print nxapi.cli_show_ascii("show system uptime")
nxapi.logout()

By default the above client will use a message format of ‘json’ but can be configured for ‘json-rpc’ or ‘xml’  through the update_format function (note, no xml parsing is performed; download the xmltodict if you prefer xml).

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s