CISCO ACI Fabric Authentication via REST API

APIC REST API Authentication

REST API Across Vendors

Modern networking switches from major vendors provide REST APIs for programmatic access:

  • Cisco — Nexus OS, Catalyst, IOS-XE, APIC
  • Arista — CloudVision and eAPI
  • Juniper — NETCONF/YANG via Junos
  • Meraki — Cloud-based REST API
Authentication Methods

APIC REST API Overview

The Application Policy Infrastructure Controller (APIC) exposes a comprehensive REST API for managing ACI fabrics. All configuration and monitoring data is accessible via standard HTTP methods (GET, POST, DELETE, PATCH).

Authentication Mechanisms

APIC supports two primary authentication methods:

  1. Token-Based (Username/Password) — Simplified, suitable for scripts and tools
  2. Signature-Based (Certificates) — More secure, recommended for production

Token-Based Login

Obtain a session cookie valid for 10 minutes (can be refreshed). After expiration, reauthenticate.

Login with cURL

You need to use the POST method aaaLogin which logs in a user and opens a session. The message body contains an aaa:User object with name and password attributes in XML format payload:

curl -X POST -k https://APIC_IP_ADDRESS/api/aaaLogin.xml -d '<aaaUser name="USERNAME" pwd="PASSWORD"/>' -c cookie.txt

The APIC controller returns a token stored in cookie.txt file, which lasts 10 minutes. The cookie must be used to run any following REST API.

Example: the REST API that uses XML format payload and creates a new Tenant, a new Application Profile, and a new EPG:

curl -b cookie.txt -X POST -k https://APIC_IP_ADDRESS/api/node/mo/uni.xml -d '<polUni> <fvTenant name="TENANT-NAME" descr="TENANT-NAME-DESC" status=""> <fvAp name="APP-PROF-NAME"> <fvAEPg name="NEW-EPG-NAME" > </fvAEPg> </fvAp> </fvTenant> </polUni>' -c cookie.txt

Login with Postman

The login with Postman requires these settings:

Method: POST — Body RAW, Type XML:

https://IP_ADDRESS_APIC/api/mo/aaaLogin.xml

<aaaUser name='USERNAME' pwd='PASSWORD'/>

You will receive a token stored in a cookie which Postman will store for you. It will last 10 minutes.

Example: Method: GET — get all NTP providers configured in the fabric:

https://IP_ADDRESS_APIC/api/class/datetimeNtpProvider.xml

If you prefer JSON format, then change REST URL to:

https://IP_ADDRESS_APIC/api/class/datetimeNtpProvider.json

Login and Automation with Python

For automating REST API usage with Python, the Session class can be used, which is available on GitHub:

import requests
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

class Session(object):
    def __init__(self, apic_ip, apic_port, user, passwd):
        self.ip = apic_ip
        self.port = apic_port
        self.user = user
        self.passwd = passwd
        self.cookie = ''

    def set_cookie(self, cookie):
        self.cookie = cookie

    def get_cookie(self):
        try:
            auth_url = "https://%s:%s/api/aaaLogin.json" % (self.ip, self.port)
            auth_json = '{"aaaUser": {"attributes": {"name": "%s", "pwd": "%s"}}}'  % (self.user, self.passwd)
            print("Getting cookie from %s" % self.ip)
            session = requests.post(auth_url, data=auth_json, verify=False, timeout=5)
            session_json = session.json()
            token = session_json["imdata"][0]["aaaLogin"]["attributes"]["token"]
            return {'APIC-cookie': token}
        except requests.exceptions.Timeout:
            print("APIC %s timed out " % (self.ip))
        except requests.exceptions.ConnectionError:
            print("Connection error can't log in to APIC %s with user %s" % (self.ip, self.user))

    def apic_json_get(self, url):
        get_json_url = "https://%s:%s/api/class/%s.json" % (self.ip, self.port, url)
        json_get = requests.get(get_json_url, cookies=self.cookie, verify=False)
        print(get_json_url)
        if json_get.status_code != 200:
            print(json_get.text)
        else:
            return json_get.text

    def apic_json_post(self, url, data):
        post_json_url = "https://%s:%s/%s.json" % (self.ip, self.port, url)
        json_post = requests.post(post_json_url, cookies=self.cookie, data=data, verify=False)
        print(post_json_url)
        if json_post.status_code != 200:
            print(json_post.text)
        else:
            return json_post.text

Usage Example:

apic_ip = 'APIC_IP_ADDRESS'
apic_port = '443'  # Default port for APIC for HTTPS
username = 'USERNAME'
password = 'PASSWORD'

# Create a session instance
session = Session(apic_ip, apic_port, username, password)

# Get the cookie
cookie = session.get_cookie()

# Set the cookie in the session
session.set_cookie(cookie)

# URL for the POST request
url = 'api/node/mo/uni/'

# Data for the POST request (JSON format)
data = '''
{
    "polUni": {
        "fvTenant": {
            "attributes": {
                "name": "TENANT-NAME",
                "descr": "TENANT-NAME-DESC",
                "status": ""
            },
            "children": [
                {
                    "fvAp": {
                        "attributes": {
                            "name": "APP-PROF-NAME"
                        },
                        "children": [
                            {
                                "fvAEPg": {
                                    "attributes": {
                                        "name": "NEW-EPG-NAME"
                                    }
                                }
                            }
                        ]
                    }
                }
            ]
        }
    }
}
'''

# Perform the POST request using the session
response = session.apic_json_post(url, data)

print(response)

Authentication and Configuration with Ansible

The Ansible ACI modules provide a user-friendly interface for managing an ACI Fabric environment using Ansible playbooks. In most cases, Ansible modules for network devices do not run on the network devices or controller but talk directly to the APIC's REST interface.

Using the aci_rest Module

Many ACI modules exist in the Ansible distribution, and most common actions can be performed with these existing modules. However, there's always something that may not be possible with off-the-shelf modules. For this purpose, the aci_rest module provides direct access to the APIC REST API and enables you to perform any task not already covered by existing modules.

The aci_rest module accepts native XML and JSON payloads and can accept an inline YAML payload. XML payload requires the path to end with .xml whereas JSON or YAML require the path to end with .json. Modifications require the use of POST or DELETE methods, whereas queries require the GET method.

Password-based authentication is very simple but not the most efficient from ACI's point-of-view as it requires login every time an Ansible task is executed and an open session. Password-based authentication may trigger anti-DoS measures in recent ACI versions resulting in HTTP 503 errors and login failures. To avoid this, use the more efficient Signature-based authentication.

Example: the following Ansible playbook creates a tenant using signature-based authentication and queries it afterward using password authentication:

- name: ACI Configuration
  hosts: localhost
  gather_facts: no
  vars:
    apic_host: 'APIC_IP_ADDRESS'
    apic_username: 'USERNAME'
    apic_password: 'PASSWORD'
    apic_private_key: 'pki/admin.key'
    apic_validate_certs: false

  tasks:
    - name: Add a tenant using inline YAML
      cisco.aci.aci_rest:
        host: '{{ apic_host }}'  # The IP address or hostname of your APIC
        username: '{{ apic_username }}'  # The username for authentication
        private_key: '{{ apic_private_key }}'  # The private key of the user for authentication
        validate_certs: false  # Whether to validate SSL certificates
        path: /api/mo/uni.json  # The API endpoint for creating a tenant
        method: post  # The HTTP method to use
        content:  # The content of the request, in YAML format
          fvTenant:
            attributes:
              name: TENANT-NAME  # The name of the tenant to create
              descr: TENANT-NAME-DESC  # The description of the tenant
      delegate_to: localhost  # Run this task on the local machine

    - name: Get tenants using password authentication
      cisco.aci.aci_rest:
        host: '{{ apic_host }}'  # The IP address or hostname of your APIC
        username: '{{ apic_username }}'  # The username for authentication
        password: '{{ apic_password }}'  # The password for authentication
        method: get  # The HTTP method to use
        path: /api/node/class/fvTenant.json  # The API endpoint for querying tenants
      delegate_to: localhost  # Run this task on the local machine
      register: query_result  # Register the output of the query in a variable

Note: the SSL certificate has nothing to do with the authentication certificate. The SSL certificate is used between APIC and client for secure communication by encrypting transmitted data. The parameter apic_validate_certs set to false avoids client validation of the certificate provided by APIC against a trusted CA.

An authentication certificate is used to verify the client's identity to APIC without using a password. APIC contains a self-signed certificate for a specific local user. The Ansible task uses the user's private key for authentication. APIC challenges the client by asking it to encrypt data using the private key contained in the file pki/admin.key. The client sends the encrypted data to APIC, which can decrypt it with the user's public key in the admin.crt file.

Usage

Save the playbook to a file (e.g., aci_config.yml) and run it using ansible-playbook:

# ansible-playbook aci_config.yml
Security Best Practice: Never hardcode passwords. Use environment variables, Ansible Vault, or external secret management systems.

References

#aci #restapi #python #ansible