Automating Cisco ASA Configuration Management with REST API

If you’re looking to gain more time for innovation, managing repeatable tasks with automation is incredibly efficient. Automating security-related tasks provides even more value than just efficiencies. It gives you an auditable workflow, which is especially valuable if you are regulated and audited regularly. Documenting your processes is much easier if it is automated. Cisco provides many ways to manage their solutions through third-party software companies or even solutions developed by your internal staff. CDA works with all types of automation tools, but today we will be focusing on the Cisco ASA REST API to address the business challenges below:

  • Firewall Administrator Turnover: When a firewall administrator leaves an organization, it’s time to change the LOCAL firewall passwords. For a larger organization, this could mean changing passwords on tens or even hundreds of firewalls.
  • Privileged Accounts and Password Rotation: As a best practice it’s important to change your passwords periodically based on a “password policy”, which is usually determined by your enterprise security, audit, or risk teams. Changing the passwords is a good idea; however, not always easy, or fun to do when you have many devices and little time.
  • Security and Compliance Requirements: Sometimes periodically changing your password is not just a good idea or best practice, it is required by your organization to maintain compliance with some audit or regulatory mandate.
  • Limited Time/Staff: Changing passwords on a firewall is not a difficult thing to do, but changing them periodically is a relatively mundane task, and should be automated. Any task you are going to perform often should be automated to minimize human error, and inefficiencies. Think of it this way if you have 100 firewalls and 10 local accounts per firewall. This would equate to roughly 1000+ commands to run across the enterprise. If each command took 1 minute to complete, the task of resetting local passwords on your firewalls would take 1000+ minutes to complete. 1000 minutes/60 minutes=16.7 hours each time the task is required.

Development Process Overview

To address these challenges, I took the following approach to build a tool that will allow for an automated and scheduled process to change Cisco ASA passwords on all devices in your inventory:

  1. SecOps Automation and Orchestration using Atomic Red Team/MITRE ATT&CK) (5 Demos)
    • T1055: Process Injection
    • T1053: Scheduled Tasks
    • T1077: Windows Admin Share
    • T1086: PowerShell
    • T1105: Remote File Copy
  2. DevSecOps: OWASP Top 10 (3 Demos)
    • SQL Injection (SQLi)
      • Broken Authenticati

Lab Overview

This script was built and tested in my lab environment. The lab consists of a few simple components as shown in the diagram below:

  • Workstation: Windows 10
    • Python 3.8
    • VSCode
  • VM: EVE-NG (Hosted on VMWare ESXi)
    • VM: Cisco-ASAV-01: 192.168.0.102 with REST API Installed
    • VM: Cisco-ASAV-02: 192.168.0.105 with REST API Installed
    • VM: Cisco-ASAV-03: 192.168.0.106 with REST API Installed

The table below gives you a general idea of what the minimal configuration should be on each firewall if you are going to set this up for yourself. Each of the Cisco ASAv(s) in my lab have the following configuration with some minor differences such as IP Address, and Host Name.

Cisco ASA-V + Configuration (Base Example)

enable password $sha512$5000$... == pbkdf2
passwd Uzp6JyHt8i6QEG34 encrypted
interface GigabitEthernet0/0
 nameif inside
 security-level 100
 ip address 192.168.0.102 255.255.255.0

logging enable
logging timestamp
logging facility 23
logging device-id context-name
aaa authentication ssh console LOCAL
aaa authentication http console LOCAL
aaa authentication login-history
http server enable
http 0.0.0.0 0.0.0.0 inside
http 192.168.0.0 255.255.255.0 inside
ssh scopy enable
ssh stricthostkeycheck
ssh pubkey-chain
  server 192.168.0.4
ssh 192.168.0.0 255.255.255.0 inside
ssh timeout 60
ssh version 2
ssh key-exchange group dh-group1-sha1
management-access inside
#Create Test Admin Users
username SecOps1 password Cisco123 privilege 15
username SecOps2 password Cisco123 privilege 15
username SecOps3 password Cisco123 privilege 15
username SecOps4 password Cisco123 privilege 15
username SecOps5 password Cisco123 privilege 15
username SecOps6 password Cisco123 privilege 15
username SecOps7 password Cisco123 privilege 15
rest-api image disk0:/asa-restapi-7141-lfbff-k8.SPA
rest-api agent

Installing the REST API on Cisco ASA-V

If you have never done so before and need to setup your Cisco ASA to host the REST API you will find this document useful: https://www.cisco.com/c/en/us/td/docs/security/asa/api/qsg-asa-api.html

Following the steps outlined in the Cisco ASA REST API Quick Start Guide you will need to install the REST API on your ASA-V if it does not exist already. This will require you to run the following commands at a minimum. Once you have the REST API installed on your firewalls you can leverage it for many tasks including the password rotation. I may build an Ansible job in my next blog to outline the steps to automate and deploy the REST API if there is interest. You will also need a TFTP server up and running with your downloaded REST API (“SPA File”):

Abbreviated Commands to Install the Cisco ASA REST API

 Login to your Cisco ASAv
 enable
 config t
 copy tftp://<YOUR IP e.g. 192.168.0.4>/asa-restapi-7141-lfbff-k8.SPA disk0:
 rest-api image disk0:/asa-restapi-7141-lfbff-k8.SPA
 http server enable
 http 0.0.0.0 0.0.0.0 inside
 aaa authentication http console LOCAL
 rest-api agent

Exploring the Cisco ASA REST API with /doc/

Before I started writing any code, I needed to get an idea of what is possible and required in my delivery of the proposed solution… I went to the REST API address to see what was possible. It is always a good idea to read the documentation when it comes to REST APIs, as there are often many examples that outline the HTTP request verbs (GET,PUT, PATCH, DELETE, etc..), syntax, payloads, and endpoints:

https:///doc/

For example, in my lab, all my firewalls have the same version of the Cisco ASA REST API. You will be prompted to login with your ASA account. You will then see the following page:

Figure 1 Initial Login

The documentation is extensive with examples and even provides you with the ability to export some examples in Python, Perl, and JavaScript. Since most of the examples were written in an older version of Python, I decided not to use them. I was able to quickly find what I needed by exploring the documentation and examples. Many of the examples provided were used in the PostMan testing I will talk about later in this blog. The management access endpoint gave me what I need to see, which protocols were enabled and configured on my devices, for example, SSH, HTTP/HTTPS, Telnet. The management access enumeration is not required to support the password rotation script, it is only included in this script to add value to any audit requests (completely optional). You will see this function later in the blog.

Figure 2 Management Access Objects

As an “old school” Cisco engineer I was naturally inclined to use the CLI option in the API (/api/cli) as depicted in the figure below. This was not a good idea because it would have caused me to poke around with a poorly formatted JSON response that included (“\n”) new line characters in the response (“no time for that”). The better option was to use the (/api/objects/localusers) object to get a list of all users and their security privileges.

Figure 3 Local User Objects

As I mentioned above, the CLI endpoint in the API is appealing in many ways but not my favorite way to get information from devices; however, a great option for configuring the devices. I will use the CLI endpoint to process the password changes in the script.

Figure 4 Fail Safe Use the CLI

Testing the REST API with PostMan

Postman is widely used for building, testing, and supporting REST APIs. It is a great tool to build and validate your requests to the Cisco ASA REST API. The first thing to do is create a collection, followed by a set of requests. I will use these for testing the response for requests (e.g. GET, POST). For this REST API I will receive all responses to each request with the content type application/json, this needs to be configured in the Body of your request.

Figure 5 Set "Content-Type" to application/json

After the collection is created, I will add some requests. The first request will need you to authenticate for “Authorization” to the REST API. I used Basic Auth, for testing. Set this at the collection level to minimize setting it on every request in Postman. If you look at the body of the sample request/response below you will see what I mean about using the CLI to get user information. Note the “\nusername” string in the standard output from the CLI request for “show run user”, formatting that type of response in JSON (painful) is time in your life you will want refunded.

Figure 6 Test Connection and Authentication

Once I successfully created my collection, set my content type, connected, and authenticated, I began validating the API endpoints I needed to incorporate into my script. The figure below shows the output of “show run” using the API CLI endpoint (“/api/cli”). There is that ugly JSON response again. For now, this will suffice for the task of getting a backup of the running configuration. I planned to write that data to a log for rollback purposes on each device.

Figure 7 Test some CLI Options. Configuration Backup

I moved on to testing (“/api/objects/localusers/”) endpoint to ensure I can see the JSON response and verify it has the data I seek for future use with the password reset/rotation automation.

Figure 8 Get LOCAL User Information

After getting usernames and privileges, I wanted to test building the JSON response body/payload for changing a single user password. For this I used an inline JSON payload as depicted below and the final script. This worked as expected and meets my needs for password reset/rotation for all accounts on all devices using an “unsophisticated bunch of for loops” in my script.

Figure 9 Test Resetting a password using the POST and CLI endpoint

I posted my PostMan collection to GitHub here.

Scripted Workflow Overview

Now that I have dissected and tested the REST API for my purposes it is time to build something useful, so I can automate the process. The process is simple and documented below:

  • Connect
  • Login
  • Backup the Configuration to Log->Screen and Log File
  • Get Management Interfaces->Screen and Log File
  • Get LOCAL Usernames and Privilege Levels->Screen and Log File
  • Change all LOCAL passwords->Screen and Log File

Cisco ASA REST API Bulk Device/Bulk LOCAL User Password Reset

#https://learninglabs.cisco.com
#https://sandboxapicdc.cisco.com/
#https://github.com/CiscoDevNet

#IMPORT PYTHON LIBRARIES

import random
import string
import requests
import urllib3
from pprint import pprint
import json
import getpass

#IGNORE CERTIFICATE WARNINGS. CHANCES ARE YOU DID NOT CREATE A TRUSTED CERT FOR ALL YOUR FIREWALL DEVICES 😊

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

#DEFINE VARIABLES FOR USE IN FUNCTIONS

log_file="fwbackuplog.log"
link_log_file="lnk.log"
user_log="user.log"
headers = {'Content-Type': 'application/json', 'User-Agent':'REST API Agent'}
cli_path = "/api/cli/"
api_path = "/api/mgmtaccess/hosts/"
user_path="/api/objects/localusers/"
openlog=open(log_file, "w")
payload={"commands":["show run user"]}
backup_payload={"commands": ["show run"]}

#PROMPT THE USER FOR USERNAME, PASSWORD, REMOVE: ENABLE PASSWORD

fw_user=input("Username: ")
try:
    fw_pwd=getpass.getpass(prompt='Password: ', stream=None)
    #en_pwd=getpass.getpass(prompt='Enable Password: ', stream=None)
except Exception as error:
    print ('ERROR',error)

#FUNCTION TO BACKUP THE RUNNING CONFIGURATION ON YOUR ASA BEFORE YOU BREAK IT 😊

def get_backups():
    openlog=open(log_file, "a")
    hosts_file = open('hosts.txt', 'r+')
    with open('hosts.txt') as hosts_file:
        hosts_array = hosts_file.read().splitlines()
        for host in hosts_array:
                url = "https://"+ host + cli_path
                print(" ")
                backupresponse=requests.post(url,auth=(fw_user, fw_pwd),data=json.dumps(backup_payload),headers=headers,verify=False)
                backup_data=json.loads(backupresponse.text)
                pprint(backup_data)
                openlog.write(backupresponse.text)
                openlog.write("\n\n")
    openlog.close()
    print(" ")
    pass

#FUNCTION TO GENERATE RANDOM PASSWORDS, THE LENGTH IS CONFIGURABLE.

def get_random_password_string(length):
    password_characters = string.ascii_letters + string.digits + string.punctuation
    password = ''.join(random.choice(password_characters) for p in range(length))
    return password

#FUNCTION TO GET MANAGEMENT INTERFACES SUCH AS HTTP, SSH, TELNET. THIS IS AN OPTION STEP THAT DEMONSTRATES OTHER FUNCTIONS OF THE API

def get_mgmtdata():
    openlog=open(link_log_file, "a")
    hosts_file = open('hosts.txt', 'r+')
    with open('hosts.txt') as hosts_file:
        hosts_array = hosts_file.read().splitlines()
        for host in hosts_array:
                url = "https://"+ host + api_path
                print(" ")
                mgmtresponse=requests.get(url,auth=(fw_user, fw_pwd),verify=False)
                data=json.loads(mgmtresponse.text)
                print(data['selfLink'])
                for i in data['items']:
                    print("type : " + i["type"],i["ip"]["value"],i["netmask"]["value"],i["interface"]["name"])
                    strType=i["type"]
                    strIP=i["ip"]["value"]
                    strNM=i["netmask"]["value"]
                    strInt=i["interface"]["name"]
                    openlog.write("Type: %s\tIP: %s\tNetmask: %s\tInterface: %s \n" % (strType,strIP,strNM,strInt))
                    openlog.write("\n")
    openlog.close()
    print(" ")


#FUNCTION TO UPDATE THE PASSWORD FOR EACH USER ON EVERY DEVICE USING THE RANDOM PASSWORD GENERATOR ABOVE. HERE I AM USING LENGTH OF 16

def update_passwords():
    openlog=open(user_log, "a")
    hosts_file = open('hosts.txt', 'r+')
    with open('hosts.txt') as hosts_file:
        hosts_array = hosts_file.read().splitlines()
        for host in hosts_array:
                url = "https://"+ host + user_path
                print("")
                print(url)
                userreq=requests.get(url,auth=(fw_user, fw_pwd),headers=headers,verify=False)
                usernameres = json.loads(userreq.text)
                for i in usernameres['items']:
                    print("Username : " + i["name"],",Privilege Level : ",i["privilegeLevel"])
                    str_username=i["name"]
                    str_privilegeLevel=i["privilegeLevel"]
                    openlog.write("Url: %s \tUsername: %s\tPrivilege Level: %s \n" % (url,str_username,str_privilegeLevel))

                print("")
                for j in usernameres['items']:
                    username=j["name"]
                    privilege=j["privilegeLevel"]
                    password=get_random_password_string(16)
                    cmdline=f"username {username} password {password} privilege {privilege}"
                    newcli='"'+ cmdline + '"'
                    _jsonpayload="{"+ '"'+"commands"+'"'+':'+"[" + newcli +"]}"
                    print(_jsonpayload)
                    openlog.write(_jsonpayload)
                    for host in hosts_array:
                        pwdurl = "https://"+ host + cli_path
                        print(pwdurl)
                        requests.post(pwdurl,auth=(fw_user, fw_pwd),data=_jsonpayload,headers=headers,verify=False)
                    openlog.write("\n")
                print("")
    openlog.close()
print(" ")


#MAIN FUNCTION, ONE FUNCTION TO RULE THEM ALL!

if __name__ == "__main__":
    #get_creds()#Get credentials for Login and access to your script to run
    get_backups()# Back it up before you start.
    get_mgmtdata()#Get Management Access Information (Optional)
    update_passwords()#This will change all passwords returned for any local accounts on the ASA!

This prototype script can be added to any job scheduler or automation engine (Jenkins), with some production ready refinements, to run on a periodic basis and allow for password rotation. I would recommend designing your logging and security since the credentials and output of this script could very easily create another audit issue because the passwords are exposed. Maybe later I will introduce some functionality to send the credentials to a vault for encrypted storage and recovery. For now, this is merely a prototype you can use to get up and running with your security automation. This script will also work well if your favorite Firewall admin just quit! If you need help building a production ready automation tool please contact me (adidonato@criticaldesign.net). This is more of a “shiv” than a samurai sword!

Script/Solution Overview

If you plan on using this script you will want to make sure you have the following:

  • Install Python
  • Install and create a Python Virtual Environment
  • Clone the repository
  • Setup your HOSTS.txt file with you list of Cisco ASA devices (inventory). This file is required to support managing multiple devices.

Test Functions (IN A TESTING ENVIRONMENT, don’t be that guy/girl!)

Script Download/Source

Disclaimer: While this may go without saying, “Do NOT test this in your production environment.”

You can access this script and supporting files at the following location. Simply “git clone” the repository and run it against your test environment.

Script Execution and Testing

adidonato@ML-UBUNTU:~$python CiscoASARESTAPI-BlogPost.py

NOTE: You will be prompted for <username> <password> <enable password>
Share this post:
Share on facebook
Share on twitter
Share on linkedin
Stay in the know with the latest content!
Blog Categories
Search By Topic
Recent Posts
Share this post:
Share on facebook
Share on twitter
Share on linkedin