Palo Alto Firewall configuration backups using the XML API via Python
Apart from monitoring and configuring the firewalls, PAN-OS XML API is also very useful for other day to day tasks initiated on tens or hundreds of firewalls. One of these quick and easy tasks is taking configuration backups via the XML API using Python.
Prerequisites
I'm running all the software on Ubuntu 20.04 server and Python 3.10.2. Lesser versions should work also, though Python < 3 has not been tested (and should not be used anymore anyways).
- Palo Alto firewalls (PAN-OS 9.0, 9.1, 10.0 and 10.1 tested with these scripts)
Getting Palo Alto configuration backup using Python script
The basics of this script is really simple. Connect to the device, request the configuration backup as XML and save it into a file. More advanced part is the use of threading which allows you to get backups from tens of firewalls simultaneously. Remember to create the backup directory beforehand, or modify the script with OS module to create directories when writing.
Note: You can easily modify the script to save the data anywhere you like. Also getting other data via the XML API can be easily implemented.
PAN-OS Login and API call functions
#!/usr/bin/env python
import json
import logging
import requests
import urllib3
import xml.etree.ElementTree as ET
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
PAApiLoginUrl = "https://{}/api/?type=keygen&user={}&password={}"
PAApiCallUrl = "https://{}/api/?type={}&action=get&xpath={}&key={}"
PAOpApiCallUrl = "https://{}/api/?type={}&cmd={}&key={}"
PAExportApiCallUrl = "https://{}/api/?type={}&category={}&key={}"
# Palo Alto API login function to get the API-key
def pa_login(fwip, fwname, username, password):
logging.debug('Logging into %s as %s', fwname, username)
# Login to Palo Alto API and get session cookie
try:
r = requests.get(PAApiLoginUrl.format(fwip, username, password), verify=False, timeout=3)
except:
logging.warning("Connect to firewall failed: %s", fwname)
return("error")
# Check that login results in proper response code
if r.status_code != 200:
logging.warning("Login failed to %s - status code: %i", fwname, r.status_code)
return("error")
else:
# If login successful, parse the XML tree and return the apikey
tree = ET.fromstring(r.content)
finder = tree.find('.//result')
apikey = finder[0].text
return apikey
# Palo Alto API call function
def pa_apicall(fwip, fwname, calltype, cmd, key):
logging.debug('Querying API on %s', fwname)
# Call API on proper URL based on calltype
if calltype == 'config':
r = requests.get(PAApiCallUrl.format(fwip, calltype, cmd, key), verify=False, timeout=10)
elif calltype == 'export':
r = requests.get(PAExportApiCallUrl.format(fwip, calltype, cmd, key), verify=False, timeout=10)
else:
r = requests.get(PAOpApiCallUrl.format(fwip, calltype, cmd, key), verify=False, timeout=10)
if r.status_code != 200:
logging.info("API call failed at %s - status code: %i", fwname, r.status_code)
return("error")
else:
return r
PAN-OS Backup configuration script
#!/usr/bin/env python
import os
import logging
import requests
import json
import urllib3
import time
import sys
import threading
# Import functions from pa_functions
from pa_functions import pa_login, pa_apicall
logging.basicConfig(level=logging.INFO, format='[%(asctime)s] [%(levelname)s] (%(threadName)-10s) %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
sem = threading.Semaphore()
# Parse JSON function
def parse_json(json_path):
with open(json_path, 'r') as json_file:
json_data = json.load(json_file)
return json_data
# Get Firewall backups function
def getBackup(fwname, fwip, username, password):
# Get the API key
key = pa_login(fwip, fwname, username, password)
# Check that the login was properly done and API key is retrieved
if "error" in key:
logging.warning('Error backing up configuration on %s', fwname)
else:
# Get the configuration output via API call
logging.debug('Backing up configuration on %s', fwname)
output = pa_apicall(fwip, fwname, "export", "configuration", key)
# Write the data to the backups/ directory with name suffix '-conf'
sem.acquire()
with open("backups/" + fwname + "-conf", 'wb') as outfile:
outfile.write(output.content)
sem.release()
def main():
logging.info('Starting Palo Alto configuration backup script...')
# File to read the firewalls from and parse the JSON
firewallSource = "firewalls.json"
fws = parse_json(firewallSource)
# File to read the config from and parse the JSON
configSource = "config.json"
config = parse_json(configSource)
# Initiate jobs list
jobs = []
for fw in fws["firewalls"]:
# Append backup function calls to threading jobs list
thread_apicall = threading.Thread(target=getBackup, args=(fw["name"], fw["ip"], config["username"], config["password"]))
jobs.append(thread_apicall)
# Start the jobs in list
for j in jobs:
j.start()
# Join the jobs in list
for j in jobs:
j.join()
if __name__ == "__main__":
main()
Example config JSON
{
"username": "USER",
"password": "PASS"
}
Example firewall source JSON
{
"firewalls": [
{
"name": "Firewall 1",
"ip": "192.168.1.1"
},
{
"name": "Firewall 2",
"ip": "192.168.1.2"
}
]
}
Verify configuration backup
Configuration backup is written (by default) to backups/ directory. Check that the configuration files are being written properly and content is valid.
Conclusion
Following the steps above allows you to grab quick and dirty configuration backupds using the PAN-OS XML API.
All the scripts above can be found in the GIT repository