Automating the Cisco Identity Services Engine Monitoring and Troubleshooting Node (ISE MnT) REST API

In my opinion Network Access Control (NAC) using a mix of 802.1x and MAC Address Bypass (MAB) with dynamic Access Control Lists (dACL) is not just an extremely important foundation of a modern, high secure, enterprise network. It is actually a Software Defined Network (SDN) that completely changes the operationalization and dynamically adjusts the very configuration of your devices.

Cisco Identity Services Engine (ISE) uses Policy Sets to achieve this providing policy for Authentication – validating the identity a device claims to be – and Authorization – what, if anything, that identified device is permitted access to. Using either certificates and the 802.1x protocol or, if certificates are unfeasible, a MAC-based permission that pushes a dynamic ACL to the switch limited the access that MAC address has on the network.

I’ve implemented this at scale – and now I am trying to use automation tools to help operate, monitor, troubleshoot, and generally understand the impact of my ISE policy sets at the Access layer. And while ISE provides some amazing GUI-based tools – the Live Logs, Live Session, Policy Sets, and Context Visibility – if I can avoid using a GUI, sorting, filtering clicking, menu-driven system – I would like to just get right to the data!

Enter: Monitoring and Troubleshooting Nodes

In your ISE deployment options you can deploy Monitoring nodes / personas

These personas come with REST APIs !

But these are not to be confused with the ISE External REST Service (ERS) APIs!

(You can easily tell the difference in the API URL noting the presence of /mnt or the /esr path respectively)

High-Level Goals

In an Ansible Playbook

  1. Get a Total Session count using the ActiveCount REST API
  2. Get an Active Session details list using the ActiveList REST API
  3. Per-Access Layer send all MAC Addresses with an Authentication Session to the Session/MAC REST API
  4. Transform all returned data into business-ready CSV files

The Tools

A lot of different automation and infrastructure as code tools were used to create this solution including:

  • Cisco ISE MnT Node – REST API Source
  • VS Code – used to write the code
  • VS Code Extensions – used to help write the code
  • Git – used to version and source control the code
  • Azure DevOps Git Repository – used to host the Git repository
  • Postman – used to investigate and develop against the REST API
  • Ansible – automation and orchestration engine driving the solution
  • Ansible Vault – used to encrypt API credentials
  • Ansible Module: URI – module used to interface with REST API
  • Ansible Module: XML – module used to work with XML data
  • Ansible Module: Copy – move data from a variable into a file
  • Ansible Module: Template – call a Jinja2 template as a source to template another filetype as the destination output file
  • Ansible Module: ios_command – run a Cisco IOS CLI command
  • Ansible Module: set_fact – create your own variable
  • Cisco pyATS Genie Parser – transform Cisco IOS command output into structured JSON
  • Filter Plugin: parse_genie – a custom Python file used with Genie to parse CLI output
  • Ansible Utilities: CLI_Parse – parse “CLI” commands
  • CLI_Parser: XML – specify XML as the filetype to feed the CLI_Parser
  • Ansible Filter: to_nice_json – create human readable “pretty” JSON files
  • Ansible Filter: dict2items – transform a dictionary to structure items (list)
  • Ansible Filter: json_query – Use SQL-like structured Queries against JSON
  • Ansible Filter: flatten – flatten a nested list
  • Ansible Filter: hwaddr – manipulate MAC address formats
  • Ansible Filter: upper – change a string to UPPER CASE
  • Jinja2 Templates – Use Pythonic code to structure a template of another filetype using the JSON data as a source
  • CSV – The ultimate business-ready output file format

Wait – did you say XML ?

Yes. For some reason ISE MnT Nodes do not return structured JSON at all and you can only receive XML format.

Unacceptable indeed!

Raiding the Lost REST API

Now that we’ve identified this limitation we have another step to consider in our automation orchestration – that is to parse and transform – the XML to JSON so we can work with it in Jinja2

Use Case #1 – Active Sessions

ISE MnT API requires some specific permissions under a user account before you get going:

As with any API development I like to start with Postman.

Setup a Postman Collection called Cisco ISE

Add the username and password under Authentication – Basic Auth

Your GET string to get to the Active Sessions API is as follows:

https://ise.domain.com/admin/API/mnt/Session/ActiveCount

With very simple headers:

Resulting in the following data set:

Now we have the components we need – the working credentials, URL string, headers, and expected output – we can migrate the code over to Ansible using the URI module.

First we need to set up some variables we can use to connect to the ISE MnT API. I use a file called Enterprise.yml for this in group_vars

Then I can proceed with my playbook called CiscoISEActiveSessionTotals.yml

The first task is to use the URI module and register the results from the ActiveCount MnT API into a variable ActiveCount_XML

Next, because it is only a single value, we can simply use XML to parse out that field as follows and register the new variable FilteredCount

I like to always capture a .json file of the data as a RAW artifact of the unmanipulated data:

Which looks just like the Postman output:

It should be noted that the above file is the contents of FilteredCount exactly.

Now I can pass this along to the Jinja2 template:

Which looks like this:

And results in this:

Very nice!

Using this as a foundation can we transform the more advanced APIs that return more than a single XML value?

Let’s find out!

Use Case #2 – Active Session Details

Adding the next API as a Request to the Postman Cisco ISE collection

Which returns a data set like this:

We will need another way, beyond XML and xpath, to parse this output. Ideally we could find a pre-made read-to-go XML to JSON conversion utility.

I tried, and failed, several different parsing tools including the Ansible recommended XML filter parse_xml with a spec file – but I just couldn’t figure this out.

Fortunately Ganesh Nalawade, Principal Engineer at Ansible, reached out to me with a great utility

So first we go out to the ActiveList REST API and register the ActiveList_XML

Now, after installing the Utilities from Ansible Galaxy, we simply feed the text ActiveList_XML.content – into the XML parser – and register the parsed data into a new variable ParsedActiveList

So lets copy that over to a .json file and take a look

Which results in:

Alright we have the XML parsed over to JSON! Now we can template it!

Looking at the JSON above we now need to loop over the activeSession under ParsedActiveList.parsed.activeList

In Jinja2 it looks like this:

Where we pick and choose our fields and the order we want to comma separate them. I like to include the | default(“N/A”) filter to add a value of “N/A” to any empty or non-present value.

The resulting CSV looks like this!

Now this is filtered against my user name but I have all 8,000+ authentication sessions, one per row, in this CSV file!

Easy filtered (as seen above) and sorted. Searchable. Simple.

What else can we do with this API?

Use Case #3 – Per-MAC Session Details

Now in my pièce de résistance we are going to add another parsing technology I love – the Cisco Genie Parser – to capture MAC addresses to feed the last Cisco ISE MnT REST API – the MAC Session.

Add this final Request to your Cisco ISE Postman Collection replacing the MAC with either a Postman variable or a MAC that has a session in ISE.

Which returns:

Ok so now that we know we can send any MAC with a session in ISE against the REST API – how can we dynamically find and feed those MAC addresses from the network?

Answer: Genie

In order to get a list of authentication sessions on a Cisco switch we use the show authentication sessions command at the CLI

So the first thing I do is check the Genie Parser library and see if they have an available parser for the command and for what Cisco platforms they support.

Sure enough – there is a parser for the command I need.

So back to the Ansible playbook – first we need to run the ios_command and run the show authentication sessions command registering the results into a variable. This playbook needs to be scoped for the Access layer hosts where the 802.1x / MAB boundary is enforced.

Then we use the Genie Parser to transform the RAW CLI standard output (stdout) to – you guessed it – JSON ! We only set this fact (the variable) when the output does NOT equal “No sessions currently exist” in case there are no authentication sessions present on the device.

Now we don’t need this output in a JSON file but we do need to parse it, or query it, in order to get the MAC addresses from the results in order to send them to the ISE MnT API.

JSON_Query is another extremely potent Ansible filter that works like an SQL query but against the structured JSON. It can take some getting used to (painful laughing) but once you get the hang of the syntax it’s incredible fast and powerful.

I owe a big shout out to my pair-programming partner who eventually figured this out with (for?) me

To break this apart:

We set a variable up jquery to hold the actual query itself.

Then we set a fact which is the pyats_auth.interfaces value, which we:

  • filter from a dictionary to items (a list with keys and values)
  • JSON_Query with our jquery string
  • Flatten down the list (since we just need the nested value.client key)

Now we have another list, MACList, that contains a list of MAC addresses from the JSON_Query, which is querying the JSON we used Genie to convert from the IOS command!

From here it’s a simple matter of feeding the API each MAC in that list in a loop and registering the results:

I want to draw your attention to additional filters I had to use. Primarily because I was stuck on “Why isn’t this working? It should work .. but it doesn’t work” for a long time at this step. Eventually using debug I printed myself the variable MACList when I spotted something – the format of the MAC address!

The Cisco IOS MAC format is different than the Cisco ISE MnT REST API expects!

Meaning I was feeding the API:

ab00.cd11.ef22

Instead of:

AB:00:CD:11:EF:22

So by adding the hwadd(‘linux’) filter it changed the structure of the MAC address.

Then by adding the upper filter it changed the lower case letters to upper case.

Once I figured this out – it all started to work.

So now we have the XML in MACSessionDetails which we again need to parse with the Ansible Utility CLI_parse

Now we need another loop here to loop over each result and we can use the .content key being the {{ item }}

So now we can actually create our.json file from the parsed XML

Which looks like this:

Now Jinaj2 templating JSON lists are a funny thing. Because we get a natural list back, as indicated by the square bracket after results [ we can loop into this “directly”.

Meaning

{% for result in ParsedActiveSessionMACList.results %}

{{ result.parsed.sessionParameters.user_name }}

{% endfor %}

NOT

{% for result in ParsedActiveSessionMACList.results %}

{{ ParsedActiveSessionMACList.results[result].parsed.sessionParameters.user_name }}

{% endfor %}

Which returns:

Success!

What I find neat:

  • Never need to sign into ISE except for deep audits – which I have the ID in the CSV to look up
  • At the L2 Access Layer I am actually, without DNS, getting the hostnames / usernames / IP addresses of the connected devices! Pretty cool!
  • At scale either in batch or on-demand I can get this data in seconds!
  • I wouldn’t classify anything I am doing as complicated – yes there are a lot of little pieces but they all fit together nicely.
  • I no longer fear XML returning from a REST API
  • I’ve fully automated the 3 key ISE 2.x (3.x has even more) MnT REST APIs!