cancel
Showing results for 
Search instead for 
Did you mean: 
cancel
Announcements

Custom Ansible Module to interact with ISE Session API

349
Views
0
Helpful
0
Comments
Mike.Cifelli
VIP Advocate

I am going to provide a general understanding on how to use a custom Ansible module. A module is essentially a reusable standalone script that Ansible will run on your behalf. The module can be run remotely and/or locally. Modules have the ability to provide a defined interface, will accept arguments, and return information to Ansible by printing a JSON string to stdout before exiting.  Sometimes you may want to rely on a custom module if you desire functionality that is not available in any of the already existing modules found in any of the Ansible collections. Custom modules let you choose any programming language and follow your own rules.

 

In the brief example I am going to provide I will show you how to break out into a python module that will interact with a Cisco ISE Session API, handle XML data manipulation, and finally return the JSON result back to Ansible. There are a couple of pre-reqs. The important one for now, is that you must add the module locally to the appropriate directory so that Ansible has the ability to find and execute it (see here for more: Adding modules and plugins locally — Ansible Documentation).  Also, AFAIK Ansible does not really have a great way to natively handle API interaction when results are in XML format, hence my desire to rely on Python via a custom module to do so.

 

The goal here is to allow Ansible admins (perhaps a server team, etc.) the ability to run a job (playbook) that consumes the ISE API so that the Ansible/server admin can use the client detail for troubleshooting and/or verification.  From experience granting this ability saves a ton of time from a network perspective.  We no longer have to get interrupted to verify and answer certain concerns.  We are essentially giving the other side a slice of the pie by providing some context visibility.  

 
To find the exact xml tags I wanted to grant visibility on I actually used a simple curl command from a linux server cli.  The curl command used was as follows (update ers user/pass and MNT url):
curl -k --include --user user:pass --request GET https://iseMNTnode/admin/API/mnt/Session/MACAddress/xx:xx:xx:xx:xx:xx
After this I was able to parse the returned xml data which I then referenced inside the python code.
 
 
Overview and walkthrough of each:
-----
Ansible Playbook:
---
- name: ISE Module
hosts: localhost
gather_facts: False
tasks:
- name: Getting Client Detail from ISE
ans_mod_ise_client_detail: --->Invoke custom module
hw_addr: "{{ hw_addr }}" --->User variable

Custom Python Module:

#!/usr/bin/python
import requests
import xml.etree.ElementTree as ET

from ansible.module_utils.basic import AnsibleModule

def main():
# Define your module arguments that a user will pass to the module
# In my case for this module I only need the client MAC Address
module_args = dict(
hw_addr=dict(type="str", required=True),
)

# Create an instance of AnsibleModule class
# Used to work with Ansible
module = AnsibleModule(argument_spec=module_args)
result = dict(changed=False, msg="")

# Extract the arguments provided from user
hw_addr = module.params["hw_addr"]

# ISE API Interaction
# Update uri with proper ISE IP address
# Update ISE ers user/pass
API_DEVICE = "https://ip/admin/API/mnt/Session/MACAddress/" + hw_addr
API_ERS_USER = "user","pass"

# Store results as variable
r = requests.get(url=API_DEVICE, auth=API_ERS_USER, verify=False)

# Import returned XML data
# fromstring() parses XML from a string directly into an Element, which is the root element of the parsed tree.
tree = ET.fromstring(r.content)

# Create result and extract desired XML output
# findtext finds text for the first subelement matching match. match may be a tag name or a path
ise_data = {
"Endpoint is connected to SDA NAD": tree.findtext('nas_ip_address'),
"Endpoint ip address is" : tree.findtext('framed_ip_address'),
"Endpoint is connected to the following interface": tree.findtext('nas_port_id'),
"User and Hostname" : tree.findtext('user_name'),
"Client Network Authorization Info" : tree.findtext('selected_azn_profiles'),
"Client Posture Status" : tree.findtext('posture_status'),
}
result["msg"] = ise_data
# After successful module execution, pass the key/value results and exit module
module.exit_json(**result)

if __name__ == "__main__":
main()

Lastly, the python code (module) above can be referenced as a boiler template to understand how to create a generic ansible module.  I have been able to follow this to piece together additional modules for API interaction as well as other things.  Just follow the steps/notes and test accordingly.  Of course update necessary items to meet your environment needs.  Other pre-reqs not mentioned are: 

-Ansible module documentation

-ISE ERS setup/config

-Ansible deployment/setup/config

 

HTH! Cheers!