### APIC Authentication with Python

We covered authenticating against the Cisco ACI APIC controller using cURL a while back… and that’s pretty useful when you need to integrate with some quick-and-dirty shell scripts.

However, many people like Python for systems programming these days as it provides more advanced data structures and a wealth of libraries to enhance your code.

We’re planning a larger series around APIC automation with Python, but if you have some Python experience you may want to start playing around right now. So we’ll cover authentication as a first step. After all, the APIC will not let you do much if you don’t give it the right credentials.

Prework: You need to have a Python 3.x environment on your computer. Check out Setting Up A Python Environment if you don’t have Python 3.x installed yet.

We’re using Jupyter notebooks for our authentication examples, which means you can download the notebook, read the included text descriptions, and run it interactively in your own environment to see what each step is doing. I’ve personally found this helpful when learning-by-example. To launch a Jupyter notebook, simply activate your Python environment and type “jupyter notebook”. If you don’t have Jupyter installed, simply type:

pip install jupyter

## Let’s Get Started!

To begin our ACI+Python journey, there is a sample notebook (aci-auth.ipynb) in the github repo where we’ll be adding Python code. You can clone the repository if you’d like (recommended), or just copy/paste to use what you want.

For a quick start, type “python” on your machine to enter the interpreter (note, if you’re using Python 2.x, please see our aci-python wiki pages for options on installing Python 3.x).

\$ python
Python 3.7.1 (default, Dec 14 2018, 13:28:58)
[Clang 4.0.1 (tags/RELEASE_401/final)] :: Anaconda, Inc. on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import requests
>>> import json
>>> 

These two lines tell Python to use the requests module, which is needed for constructing HTTP requests, and the JSON module to decode the JSON response objects from the APIC. For Python work, we generally prefer JSON over XML, as JSON translates well to native Python data structures (lists, dictionaries, etc).

If you get errors when running the import commands, exit with CTRL-D and then try installing them with:

pip install requests
pip install json

To create an authentication request, we need to send an HTTP POST with user/password data. The most straightforward way to do this, if you were to open up a Python interpreter at the command prompt (eg, by typing “python” in a terminal window):

>>> r = requests.post('https://YOUR_CONTROLLER/api/aaaLogin.json', json={"aaaUser":{"attributes":{"name":"YOUR_USERNAME","pwd":"YOUR_PASSWORD"}}}, verify=False)

(I don’t really have to tell you to replace YOUR_CONTROLLER, YOUR_USERNAME, or YOUR_PASSWORD with your information, do I? Good!)

This does several things:

• We use “requests.post()” method for HTTP POST.
• We pass the URI for controller authentication to requests.post().
• We also pass a Python dictionary (key/value pairs). The dictionary is encoded as JSON with the “json={“some”:”data”}” stuff.
• “verify=False” tells the requests module to accept the SSL Cert without validation. If you’re using a valid SSL certificate on our controller, you don’t need this.

We then assign the resulting output to an object, “r”.

We can then do interesting things with that object, such as call the json method, json(), to decode the JSON data:

>>> r.json()

{'totalCount': '1', 'imdata': [{'aaaLogin': {'attributes': {'token': 'Y40GAAAAAAAAAAAAAAAAAFmfEK6k7mIH4WqIgDDXVoAz8HOA77OV8WdA4wJG2RK/3N7DAuG5UnXHRIn1Oz+4i0oEo5UJ5uE3NXBXCpv9ajho8evGwPz3G9FqMV2yuHSxSf2eB24e2kEzMO3JDlVL3bz8QvymsaUd4dsYxJmCl5t/rGE60w4ewJypwsw7ez82QoKe9BpucEXNJjqHRwy7uiqmxrsUQSxlP1DdR5NB63A0meLLsiLCCsrZFCFTgElC', 'siteFingerprint': '2hlEKPs8VI2HTh7E', 'refreshTimeoutSeconds': '3600', 'maximumLifetimeSeconds': '86400', 'guiIdleTimeoutSeconds': '3600', 'restTimeoutSeconds': '90', 'creationTime': '1551388790', 'firstLoginTime': '1551388790', 'userName': 'admin', 'remoteUser': 'false', 'unixUserId': '15374', 'sessionId': '

(output ad infinitum....)

There’s a lot to unpack here, but for our purposes we just need that token. See where it says: ‘token’:’Y40GAAAAAAAAablahblahblah’ ? We need that long string there, and we’ll want to send that info back with any future requests to prove to the APIC that we are, in fact, the person who just gave it valid username/password.

(Sidenote: you can also get this token from the HTTP response headers rather than the HTTP body from the controller’s response. We cover that in more detail in the aci-auth example notebook.)

To send a second request, say to grab all endpoints in a fabric, we simply create a new request and send that token in the HTTP headers. And we can do that like this:

>>> r_json = r.json()
>>> r_ep
<Response [200]>
>>> r_ep.json()


So what’s all this?

First, we decode the JSON response object, and store it as “r_json”.

Next, we extract the token from the r_json and assign it to the variable “token”. If you’re not familiar with Python lists and dictionaries, we’re looking for the token in a set of nested data in the response. We’ll cover this more in a future post, but for now, this is a simple way to grab the token. If you want, you can run “print(token)” to verify the token was assigned correctly.

We create variable called “cookie”, and construct a key/value pair as a Python dictionary using the key “APIC-cookie” and token variable we extracted from our authentication response.

Then, we use the requests.get() method again to send an HTTP request to the controller, only this time it’s to the “node/class/fvCEp.json” URI. We pass the cookie as an option using the parameter “cookies” (you can send more than one, hence the plural), and disable SSL verification. This will get you a listing of all global endpoints attached to application profiles/endpoint groups and those attached to VRFs for L3Out.

Finally, we checked for the status by typing “r_ep” and saw we got an HTTP 200 response. If we had gotten a 403, it might indicate the token had expired and we need to resend the earlier auth request to get a fresh token.

Since we have a 200 response, we decoded the response data with the .json() method on the r_ep object and if the stars align and the Buddha smiles upon us), we have a list of endpoints obscured in the rather obtuse-looking data.

So far, we’ve just been entering commands directly into the Python interpreter. What would this look like as a Python script? Probably something like this:

#!/usr/bin/env python
import requests
import json
r_json = r.json()
ep_json = r_ep.json()
print(json.dumps(ep_json, indent=4, sort_keys=True))

Ta-da!

I added a couple of lines at the bottom to assign the JSON decoded data as “ep_json”, and then did a “pretty print” on it to make it look nice.

I also added a shebang line at the top, indicating we should execute this script with the Python interpreter, with the specific version of Python taken from our environment (this assumes you used “source activate” to get your Python3 virtualenv running). You can also specify the hard path to python3, kinda like this on my Mac:

#!/Users/andrew/homebrew/bin/python3

This line shows a path to a Homebrew intallation where I installed python3 into a custom location, but I prefer to configure virtualenvs. We have more info on this in the github wiki for the aci-python code.

And that’s all you need to do to authenticate against the APIC in Python. We explore some other ways to grab that token from the HTTP headers, and include an example creating a request session so that you don’t even have to do that much.

All that and more is covered in the aci-auth notebook.

As always, if you have questions or comments, feel free to share them below!

## One thought on “APIC Authentication with Python”

1. Just wanted to say, your articles are great! This has been my best ACI resource to date! 😀

This site uses Akismet to reduce spam. Learn how your comment data is processed.