ACI access via external scripts

After we’ve mastered our access to an ACI fabric via Postman – how about the same thing from a script?

Why a script? Firstly – this will give you a deeper understanding how this is working. And secondly – using a script gives you more control, especially if you are planning to automate things.

Just a simple example – you want to create in an automated way changes into the fabric – in the end unattended and in the background. There a so many tools to support you.

Workflow could be:

  • create your demand as a database entry and trigger the build process
  • details are being pushed out to a config file
  • your delivery environment (e.g. GoCD ) detects this config file and
  • launches your script (python, ansible, shell, whatever you prefer)
  • changes will be implemented failsafe (in theory 🙂 as we all know – life can be guileful)

The procedure is always the same – send a post request to the API, and check have a look into the results.

Using Curl in a shell script

We’ll start with a simple curl command line – to login into the box.

The POST url we’ve to use is

https://192.168.140.40/api/aaaLogin.json

The payload is

{ "aaaUser" : { "attributes": {"name":"admin","pwd":"totalsecretpassword" } } }

We’ll put this together in a curl command line (–insecure is needed, as we don’t have a valid cert when accessing per IP):

curl -i --insecure -H "content-type:application/json" -XPOST https://192.168.140.40/api/aaaLogin.json -d '{ "aaaUser" : { "attributes": {"name":"admin","pwd":"totalsecretpassword" } } } '

And our APIC answers back (as we’ve seen in the postman article):

HTTP/1.1 200 OK
Server: Cisco APIC
Date: Thu, 02 Jul 2020 14:14:31 GMT
Content-Type: application/json
Content-Length: 1791
Connection: keep-alive
Set-Cookie: APIC-cookie=eyJhbGciOiJSUzI1NiIsImtpZCI6Ijh4MWp4bWN5aTRkamZqYWd2anVxbmxwdjNtZ2EzNDUyIiwidHlwIjoiand0In0.eyJyYmFjIjpbeyJkb21haW4iOiJhbGwiLCJyb2xlc1IiOjAsInJvbGVzVyI6MX1dLCJpc3MiOiJBQ0kgQVBJQyIsInVzZXJuYW1lIjoiYWRtaW4iLCJ1c2VyaWQiOjE1Mzc0LCJ1c2VyZmxhZ3MiOjAsImlhdCI6MTU5MzY5OTI3MSwiZXhwIjoxNTkzNjk5ODcxLCJzZXNzaW9uaWQiOiJsdVdtdUNBTFFWcStaQzJhckdNRFRBPT0ifQ.kWQcxvw-9cIMP1XAZsTvBs4rtkXWevC2ZC1ONcjsJefMpYWbP8PgiYW6DpW-QLgGDdc8TDL1xws1nG0jPqKneppRgklcI9BygTm3IywkyaWvIEYZMOs0uvWyT8GCFKCPyaqQwAE_m725PLECFIPk4RVfG0dyDi4qca08AtjzGaHhvhp3WsX55XrcBtxA_1c9ZgcHDjhK1NNbOm3HASHa5sZNcpMPogd0ya-vpUrePlw5yrP559gHy2gUhLybANcIkFBCptCUj8X7PETTJi9DEpcPm5mUZTSUw3bcQtHc_rKRA3lTGJWbrOuZTQRCreb7ygaES7SKXYp-3hWAem5FjQ; path=/; HttpOnly; HttpOnly; Secure
Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, DevCookie, APIC-challenge, Request-Tag
Access-Control-Allow-Methods: POST,GET,OPTIONS,DELETE
X-Frame-Options: SAMEORIGIN
Strict-Transport-Security: max-age=31536000; includeSubdomains
Cache-Control: no-cache="Set-Cookie, Set-Cookie2"
Client-Cert-Enabled: false
Access-Control-Allow-Origin: http://127.0.0.1:8000
Access-Control-Allow-Credentials: false

{"totalCount":"1","imdata":[{"aaaLogin":{"attributes":{"token":"eyJhbGciOiJSUzI1NiIsImtpZCI6Ijh4MWp4bWN5aTRkamZqYWd2anVxbmxwdjNtZ2EzNDUyIiwidHlwIjoiand0In0.eyJyYmFjIjpbeyJkb21haW4iOiJhbGwiLCJyb2xlc1IiOjAsInJvbGVzVyI6MX1dLCJpc3MiOiJBQ0kgQVBJQyIsInVzZXJuYW1lIjoiYWRtaW4iLCJ1c2VyaWQiOjE1Mzc0LCJ1c2VyZmxhZ3MiOjAsImlhdCI6MTU5MzY5OTI3MSwiZXhwIjoxNTkzNjk5ODcxLCJzZXNzaW9uaWQiOiJsdVdtdUNBTFFWcStaQzJhckdNRFRBPT0ifQ.kWQcxvw-9cIMP1XAZsTvBs4rtkXWevC2ZC1ONcjsJefMpYWbP8PgiYW6DpW-QLgGDdc8TDL1xws1nG0jPqKneppRgklcI9BygTm3IywkyaWvIEYZMOs0uvWyT8GCFKCPyaqQwAE_m725PLECFIPk4RVfG0dyDi4qca08AtjzGaHhvhp3WsX55XrcBtxA_1c9ZgcHDjhK1NNbOm3HASHa5sZNcpMPogd0ya-vpUrePlw5yrP559gHy2gUhLybANcIkFBCptCUj8X7PETTJi9DEpcPm5mUZTSUw3bcQtHc_rKRA3lTGJWbrOuZTQRCreb7ygaES7SKXYp-3hWAem5FjQ","siteFingerprint":"8x1jxmcyi4djfjagvjuqnlpv3mga3452","refreshTimeoutSeconds":"600","maximumLifetimeSeconds":"86400","guiIdleTimeoutSeconds":"1200","restTimeoutSeconds":"90","creationTime":"1593699271","firstLoginTime":"1593699271","userName":"admin","remoteUser":"false","unixUserId":"15374","sessionId":"luWmuCALQVq+ZC2arGMDTA==","lastName":"","firstName":"","changePassword":"no","version":"5.0(1k)","buildTime":"Wed May 13 23:24:01 PDT 2020","node":"topology/pod-1/node-1"},"children":[{"aaaUserDomain":{"attributes":{"name":"all","rolesR":"admin","rolesW":"admin"},"children":[{"aaaReadRoles":{"attributes":{}}},{"aaaWriteRoles":{"attributes":{},"children":[{"role":{"attributes":{"name":"admin"}}}]}}]}},{"DnDomainMapEntry":{"attributes":{"dn":"uni/tn-common","readPrivileges":"admin","writePrivileges":"admin"}}},{"DnDomainMapEntry":{"attributes":{"dn":"uni/tn-mgmt","readPrivileges":"admin","writePrivileges":"admin"}}},{"DnDomainMapEntry":{"attributes":{"dn":"uni/tn-infra","readPrivileges":"admin","writePrivileges":"admin"}}}]}}]}

As this works – we’ll put this into a bash script.

#!/bin/bash
#
# Script to login to APIC via curl
# 

# Credentials
user=admin
pwd=donttell
# Curl Post
curl -i --insecure -H "content-type:application/json" -XPOST https://192.168.140.40/api/aaaLogin.json -d '{ "aaaUser" : { "attributes": {"name":"'$user'","pwd":"'$pwd'" } } } '

printf "\n\n Done\n\n"

# End of script

Works as designed – nice.

In the end – we’ll follow the same approach as we did with Postman.

The only difference – we’ve to store the session cookie we’ll get after the login (named APIC-cookie) and hand it over to the curl command to create the test tenant.

#!/bin/bash
#
# Script to create a tenant via curl
# 

# Credentials
user=admin
pwd=sosecretsosecret
# Curl Post to get logged in
# Cookie is being stored in file named "Cookie"
curl -i --insecure -H "content-type:application/json" -XPOST https://192.168.140.40/api/aaaLogin.json -d '{ "aaaUser" : { "attributes": {"name":"'$user'","pwd":"'$pwd'" } } } ' -c Cookie
printf "\n\n Logged in\n\n"

printf "\n\n Create tenant\n\n"
# Curl Post to create the tenant
# Cookie being read in 
curl -i --insecure -H "content-type:application/json" -XPOST https://192.168.140.40/api/node/mo/uni/tn-Beaker.json -d '{"fvTenant":{"attributes":{"dn":"uni/tn-Beaker","name":"Beaker","nameAlias":"TheLabGuy","descr":"A simple test tenant with the magic bash script","rn":"tn-Beaker","status":"created"},"children":[]}}' -b Cookie


printf "\n\n Done\n\n"

# End of script

Run the script and have a look on your APIC console.

The most obvious advantage of using a script instead of Postman – you don’t need to install a huge software package (OSX version has a 350 MB size), much leaner.

On the other hand – you’ve got much more options at hand by using Postman, so as always – you’ve to decide which approach fits best. In most cases – both – depends on your use case.

JSON PP (Pretty Printer)

If you modify the curl call a little bit, you are able to use tools like json_pp (man json_pp).

curl  --insecure -H "content-type:application/json" -XPOST https://192.168.140.40/api/aaaLogin.json -d '{ "aaaUser" : { "attributes": {"name":"'$user'","pwd":"'$pwd'" } } } ' | json_pp

This will pipe the output into json_pp and the answer is much easier to read.

{
   "totalCount" : "1",
   "imdata" : [
      {
         "aaaLogin" : {
            "children" : [
               {
                  "aaaUserDomain" : {
                     "children" : [
                        {
                           "aaaReadRoles" : {
                              "attributes" : {}
                           }
                        },
                        {
                           "aaaWriteRoles" : {
                              "children" : [
                                 {
                                    "role" : {
                                       "attributes" : {
                                          "name" : "admin"
                                       }
                                    }
                                 }
                              ],
                              "attributes" : {}
                           }
                        }
                     ],
                     "attributes" : {
                        "rolesW" : "admin",
                        "name" : "all",
                        "rolesR" : "admin"
                     }
                  }
               },
               {
                  "DnDomainMapEntry" : {
                     "attributes" : {
                        "writePrivileges" : "admin",
                        "readPrivileges" : "admin",
                        "dn" : "uni/tn-common"
                     }
                  }
               },
               {
                  "DnDomainMapEntry" : {
                     "attributes" : {
                        "writePrivileges" : "admin",
                        "readPrivileges" : "admin",
                        "dn" : "uni/tn-mgmt"
                     }
                  }
               },
               {
                  "DnDomainMapEntry" : {
                     "attributes" : {
                        "writePrivileges" : "admin",
                        "readPrivileges" : "admin",
                        "dn" : "uni/tn-infra"
                     }
                  }
               }
            ],
            "attributes" : {
               "node" : "topology/pod-1/node-1",
               "remoteUser" : "false",
               "sessionId" : "TL+Yj5bNS1+5TI8rJBGkEQ==",
               "siteFingerprint" : "8x1jxmcyi4djfjagvjuqnlpv3mga3452",
               "lastName" : "",
               "userName" : "admin",
               "token" : "eyJhbGciOiJSUzI1NiIsImtpZCI6Ijh4MWp4bWN5aTRkamZqYWd2anVxbmxwdjNtZ2EzNDUyIiwidHlwIjoiand0In0.eyJyYmFjIjpbeyJkb21haW4iOiJhbGwiLCJyb2xlc1IiOjAsInJvbGVzVyI6MX1dLCJpc3MiOiJBQ0kgQVBJQyIsInVzZXJuYW1lIjoiYWRtaW4iLCJ1c2VyaWQiOjE1Mzc0LCJ1c2VyZmxhZ3MiOjAsImlhdCI6MTU5MzcwNTE0OSwiZXhwIjoxNTkzNzA1NzQ5LCJzZXNzaW9uaWQiOiJUTCtZajViTlMxKzVUSThySkJHa0VRPT0ifQ.Fj4o4jwj2VT2KDmfdSQyrn2EkSSW8MRtjvdEUoHt4-_Bv4RKB_uVRsjaJuBo_e-TQm_dfAc07Hm46iUcqNLon7rbhyb9EqidXY855ZxbL9wJBUvLjKf4hclg3kkw_ctcGcgG6OpZNRX1oKd5NKwlDlffkqHmDB5dFWmjdTxbznyyqHwnuSzxvnRSzYeKU9Q8jVYMPfOnApcyCwyOfdwCLUktGkU0Yv1kpYWrm58gbIKiNt8O5IAHtFNTh58SA538soaHr2y-LFqaDeKrW0KubRyjPp670ocTk-Vc2L7Zh1Qz-jCcAsAyfpnaJvYza5y1FEOR_aiK1ub9AY9-Qrd4qg",
               "firstLoginTime" : "1593705149",
               "firstName" : "",
               "restTimeoutSeconds" : "90",
               "version" : "5.0(1k)",
               "creationTime" : "1593705149",
               "buildTime" : "Wed May 13 23:24:01 PDT 2020",
               "changePassword" : "no",
               "refreshTimeoutSeconds" : "600",
               "maximumLifetimeSeconds" : "86400",
               "unixUserId" : "15374",
               "guiIdleTimeoutSeconds" : "1200"
            }
         }
      }
   ]
}

Accessing ACI via a python script

As all roads lead to Rome – it is possible to use this approach with any tool you prefer. As a brief example – how to do this in python.

Python will manage this by using the json module, which has to be imported.

#!/usr/bin/env python
#
# Python-Example Script to configure ACI
#
# A. Fassl - 07/2020

# Load required modules
import json
import requests
import os

# Prepare 
print requests.certs.where()


# The API URL of the apic - via letsencrypt-delivered https
base_url = 'https://acisim.progis.net:8022/api/'

# Access Credentials
name_and_pwd = {'aaaUser': {'attributes': {'name': 'admin', 'pwd': 'PwD2020xpx'}}}

json_credentials = json.dumps(name_and_pwd)

# login via the API

login_url = base_url + 'aaaLogin.json'
post_response = requests.post(login_url, data=json_credentials, verify=False)

# get token from login response structure
auth = json.loads(post_response.text)
print auth

login_attributes = auth['imdata'][0]['aaaLogin']['attributes']
auth_token = login_attributes['token']

# create cookie array from token
cookies = {}
cookies['APIC-Cookie'] = auth_token

print cookies

And the output delivers the expected results (I haven’t taken care of the SSL cert verification – so please ignore the warning):

# ./login.py
/etc/pki/tls/certs/ca-bundle.crt
/usr/lib/python2.7/site-packages/urllib3/connectionpool.py:769: InsecureRequestWarning: Unverified HTTPS request is being made. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.org/en/latest/security.html
  InsecureRequestWarning)
{u'imdata': [{u'aaaLogin': {u'attributes': {u'userName': u'admin', u'maximumLifetimeSeconds': u'86400', u'refreshTimeoutSeconds': u'600', u'firstName': u'', u'remoteUser': u'false', u'buildTime': u'Wed May 13 23:24:01 PDT 2020', u'creationTime': u'1594019696', u'sessionId': u'hhoET4O5S0er+ANqeI6FGw==', u'node': u'topology/pod-1/node-1', u'siteFingerprint': u'8x1jxmcyi4djfjagvjuqnlpv3mga3452', u'token': u'eyJhbGciOiJSUzI1NiIsImtpZCI6Ijh4MWp4bWN5aTRkamZqYWd2anVxbmxwdjNtZ2EzNDUyIiwidHlwIjoiand0In0.eyJyYmFjIjpbeyJkb21haW4iOiJhbGwiLCJyb2xlc1IiOjAsInJvbGVzVyI6MX1dLCJpc3MiOiJBQ0kgQVBJQyIsInVzZXJuYW1lIjoiYWRtaW4iLCJ1c2VyaWQiOjE1Mzc0LCJ1c2VyZmxhZ3MiOjAsImlhdCI6MTU5NDAxOTY5NiwiZXhwIjoxNTk0MDIwMjk2LCJzZXNzaW9uaWQiOiJoaG9FVDRPNVMwZXIrQU5xZUk2Rkd3PT0ifQ.irQ52pJMojphV8RrFQ231mwxsBx1_myQmb0kF3G7nIIGgDsU38uiUDydXa_N7dzLKVZb3eOwgZT6mhBDiyMS_iDjBmdimJMU6kRQ191eGeor4WSFX1UdcvHwH_X-BJMebGjWDM2c5tDampke7Ggf5Cr17bvM6NEtWfO_F82QvTq0Whe-FJlkxUXP8wdx1dSBgVVZXGX7c-u3WsaI6SaqhztlaW5mm0DfDYPd2u98xvHM4twJwLhgcyG8vbbu_y88d4O_9RcI-IV1mmXfWHamHzgMfU2vqetzgBWtlF8OUsy-Y-Sk9b2pa7hU23dMSvyXcNiuMDO7dZZwV3CXyMzKJQ', u'version': u'5.0(1k)', u'restTimeoutSeconds': u'90', u'changePassword': u'no', u'lastName': u'', u'firstLoginTime': u'1594019696', u'unixUserId': u'15374', u'guiIdleTimeoutSeconds': u'1200'}, u'children': [{u'aaaUserDomain': {u'attributes': {u'rolesW': u'admin', u'name': u'all', u'rolesR': u'admin'}, u'children': [{u'aaaReadRoles': {u'attributes': {}}}, {u'aaaWriteRoles': {u'attributes': {}, u'children': [{u'role': {u'attributes': {u'name': u'admin'}}}]}}]}}, {u'DnDomainMapEntry': {u'attributes': {u'dn': u'uni/tn-common', u'readPrivileges': u'admin', u'writePrivileges': u'admin'}}}, {u'DnDomainMapEntry': {u'attributes': {u'dn': u'uni/tn-mgmt', u'readPrivileges': u'admin', u'writePrivileges': u'admin'}}}, {u'DnDomainMapEntry': {u'attributes': {u'dn': u'uni/tn-infra', u'readPrivileges': u'admin', u'writePrivileges': u'admin'}}}]}}], u'totalCount': u'1'}
{'APIC-Cookie': u'eyJhbGciOiJSUzI1NiIsImtpZCI6Ijh4MWp4bWN5aTRkamZqYWd2anVxbmxwdjNtZ2EzNDUyIiwidHlwIjoiand0In0.eyJyYmFjIjpbeyJkb21haW4iOiJhbGwiLCJyb2xlc1IiOjAsInJvbGVzVyI6MX1dLCJpc3MiOiJBQ0kgQVBJQyIsInVzZXJuYW1lIjoiYWRtaW4iLCJ1c2VyaWQiOjE1Mzc0LCJ1c2VyZmxhZ3MiOjAsImlhdCI6MTU5NDAxOTY5NiwiZXhwIjoxNTk0MDIwMjk2LCJzZXNzaW9uaWQiOiJoaG9FVDRPNVMwZXIrQU5xZUk2Rkd3PT0ifQ.irQ52pJMojphV8RrFQ231mwxsBx1_myQmb0kF3G7nIIGgDsU38uiUDydXa_N7dzLKVZb3eOwgZT6mhBDiyMS_iDjBmdimJMU6kRQ191eGeor4WSFX1UdcvHwH_X-BJMebGjWDM2c5tDampke7Ggf5Cr17bvM6NEtWfO_F82QvTq0Whe-FJlkxUXP8wdx1dSBgVVZXGX7c-u3WsaI6SaqhztlaW5mm0DfDYPd2u98xvHM4twJwLhgcyG8vbbu_y88d4O_9RcI-IV1mmXfWHamHzgMfU2vqetzgBWtlF8OUsy-Y-Sk9b2pa7hU23dMSvyXcNiuMDO7dZZwV3CXyMzKJQ'}