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).