Palo Alto REST API with Python

In the previous post, we covered PanOS REST API fundamentals. In this post, we will go through the example and a use case using Python.

Prerequisite

To get the most out of this tutorial, you should be familiar with basic programming - Python specifically. That said if you have a very basic understanding of Python or another programming language then you should be able to follow along.

Our goal here is to identify rules that have 'any' 'any' for both Source and Destination Addresses. As you can see below, there is only a single rule that matches the criteria. If you have thousands of rules and want to scrape through them easily, then Python + REST API is one of the best options available.

Python quick overview

'Requests' Library

There are several libraries available to make HTTP requests in Python but the widely used one is 'Requests'. Before we can do anything with this library, we need to install it first by using pip install requests command.

Python Dictionary and JSON Object

Python dictionary is a collection of key-value pairs, here is an example of a dictionary.

dict_example = {
    "host-name": "firewall-01",
    "ip-address": "10.10.1.1",
    "mask": "255.255.255.0"
}

host-name, ip-address and mask are the keys and firewall-1, 10.10.1.1 and 255.255.255.0 are the values.

In Python, the dictionary is indexed using the keys. For example, print(dict_example["ip-address"]) outputs 10.1.1.1

JSON on the other hand is very similar to a Python dictionary but is defined as string in Python. JSON objects can be nested within lists or lists nested within objects, just like a Python Dictionary.

For example, the value of the key ip-address is actually another dictionary.

dict_example = {
    "host-name": "firewall-01",
    "ip-address": {
        "primary": "10.10.1.1",
        "secondary": "10.20.10.10"
    },
    "mask": "255.255.255.0"
}

Converting JSON to Dictionary and vice-versa

By using the previous example, let's find out the Python data type of the variable dict-example.

dict_example = {
    "host-name": "firewall-01",
    "ip-address": {
        "primary": "10.10.1.1",
        "secondary": "10.20.10.10"
    },
    "mask": "255.255.255.0"
}

print(type(dict_example))

# OUTPUT:  <class 'dict'>

The output is <class 'dict'> which means it's a dictionary. Let's convert this dictionary into JSON by using the JSON library.

import json

dict_example = {
    "host-name": "firewall-01",
    "ip-address": {
        "primary": "10.10.1.1",
        "secondary": "10.20.10.10"
    },
    "mask": "255.255.255.0"
}

json_obj = json.dumps(dict_example)
print(type(json_obj))

# OUTPUT: <class 'str'>

Now the output is <class 'str'> which means it's a python string. You can now use this JSON object in your script where the requirement is to have JSON.

  • json.dumps() - convert python dictionary to a JSON object
  • json.load() - convert JSON object to a python dictionary

PanOS API

GET - Security Policies

Let's start by making an API call and retrieving all the Security Policies that are configured on the firewall. Palo Alto will send a response as a JSON object that we can then use throughout the example.

First, import the requests library to be able to make API calls. I've added the query parameters as a variable called location and the URI as api_url. The API key should also be passed into the HTTP header.

import requests
import json

# Disable self-signed warning
requests.packages.urllib3.disable_warnings()

# Rest API URI
location = {'location': 'vsys', 'vsys': 'vsys1'}

api_url = "https://palo-lab.local/restapi/v10.2/Policies/SecurityRules"

headers = {'X-PAN-KEY': 'LUFRPT0wVGgrZ2VCb082WFNKWmRha0puUnZodE9sWXc9aldDN1pQMUxMN0FDRCtIa0tUczBSbHhlTDZtZWFUb1F4d3FhSnpRTVVwS2Zgy0I0N2pTVE5TaXhQQndPbysxeQ=='}

r = requests.get(api_url, params=location, verify=False, headers=headers)

json_object = json.loads(r.text)
json_formatted_str = json.dumps(json_object, indent=2)

print(json_formatted_str)

The following is the API response we received from the firewall that contains all the rules.

suresh@mac:~/Documents/ghost-pro/palo-rest-api/post|⇒  python policy-get.py
{
  "@status": "success",
  "@code": "19",
  "result": {
    "@total-count": "3",
    "@count": "3",
    "entry": [
      {
        "@name": "Access to DNS",
        "@uuid": "a15eaa32-1220-4fdf-8f64-32675b676ab0",
        "@location": "vsys",
        "@vsys": "vsys1",
        "to": {
          "member": [
            "outside"
          ]
        },
        "from": {
          "member": [
            "inside"
          ]
        },
        "source": {
          "member": [
            "CLIENT-SUBNET"
          ]
        },
        "destination": {
          "member": [
            "Google-DNS"
          ]
        },
        "source-user": {
          "member": [
            "any"
          ]
        },
        "category": {
          "member": [
            "any"
          ]
        },
        "application": {
          "member": [
            "dns"
          ]
        },
        "service": {
          "member": [
            "application-default"
          ]
        },
        "source-hip": {
          "member": [
            "any"
          ]
        },
        "destination-hip": {
          "member": [
            "any"
          ]
        },
        "action": "allow"
      },
      {
        "@name": "Access to Internet",
        "@uuid": "b3800b64-893b-471f-a404-0e67aa7b4450",
        "@location": "vsys",
        "@vsys": "vsys1",
        "to": {
          "member": [
            "outside"
          ]
        },
        "from": {
          "member": [
            "inside"
          ]
        },
        "source": {
          "member": [
            "CLIENT-SUBNET"
          ]
        },
        "destination": {
          "member": [
            "any"
          ]
        },
        "source-user": {
          "member": [
            "any"
          ]
        },
        "category": {
          "member": [
            "any"
          ]
        },
        "application": {
          "member": [
            "ssl",
            "web-browsing"
          ]
        },
        "service": {
          "member": [
            "application-default"
          ]
        },
        "source-hip": {
          "member": [
            "any"
          ]
        },
        "destination-hip": {
          "member": [
            "any"
          ]
        },
        "action": "allow"
      },
      {
        "@name": "test-policy",
        "@uuid": "c268f51f-ca99-441c-a34e-71edc0258e8f",
        "@location": "vsys",
        "@vsys": "vsys1",
        "to": {
          "member": [
            "outside"
          ]
        },
        "from": {
          "member": [
            "inside"
          ]
        },
        "source": {
          "member": [
            "any"
          ]
        },
        "destination": {
          "member": [
            "any"
          ]
        },
        "source-user": {
          "member": [
            "any"
          ]
        },
        "category": {
          "member": [
            "any"
          ]
        },
        "application": {
          "member": [
            "any"
          ]
        },
        "service": {
          "member": [
            "application-default"
          ]
        },
        "source-hip": {
          "member": [
            "any"
          ]
        },
        "destination-hip": {
          "member": [
            "any"
          ]
        },
        "action": "allow",
        "log-start": "yes"
      }
    ]
  }
}

Let's say we want to print out just the 'Rule Name' of each policy. As we discussed before, first we need to convert the JSON object into a Python Dictionary by using the json.loads() method. We can then use a for loop to loop over each policy and print out just the "Rule Name".

import requests
import json

# Disable self-signed warning
requests.packages.urllib3.disable_warnings()

# Rest API URI
location = {'location': 'vsys', 'vsys': 'vsys1'}

api_url = "https://palo-lab.local/restapi/v10.2/Policies/SecurityRules"

headers = {'X-PAN-KEY': 'LUFRPT0wVGgrZ2VCb082WFNKWmRha0puUnZodE9sWXc9aldDN1pQMUxMN0FDRCtIa0tUczBSbHhlTDZtZWFUb1F4d3FhSnpRTVVwSftaK0I0N2pTVE5TaXhQQndPbysxeQ=='}

r = requests.get(api_url, params=location, verify=False, headers=headers)

json_object = json.loads(r.text)

# Print rule names
for n in range(len(json_object['result']['entry'])):
    rule_name = json_object['result']['entry'][n]['@name']
    print(rule_name)
suresh@mac:~/Documents/ghost-pro/palo-rest-api/post|⇒  python print-rule-names.py 

Access to DNS
Access to Internet
test-policy

As you can see above, the three 'Rule Names' are displayed on the terminal.

Find 'any 'any'

Now, let's say I want to find all the rules that have 'any' 'any' for both Source and Destination Addresses. As you can see below, there is only a single rule that matches the criteria.

import requests
import json

# Disable self-signed warning
requests.packages.urllib3.disable_warnings()

# Rest API URI
location = {'location': 'vsys', 'vsys': 'vsys1'}

api_url = "https://palo-lab.local/restapi/v10.2/Policies/SecurityRules"

headers = {'X-PAN-KEY': 'LUFRPT0wVGgrZ2VCb082WFNKWmRha0puUnZodE9sWXc9aldDN1pQMUxMN0FDRCtIa0tUczBSbHhlTDZtZWFUb1F4d3FhSnpRTVVwS2ZaK0I0N2pTVE5TaXhQQndPbysxeQ=='}

r = requests.get(api_url, params=location, verify=False, headers=headers)

json_object = json.loads(r.text)

for n in range(len(json_object['result']['entry'])):
    if json_object['result']['entry'][n]['source']['member'][0] == 'any' and json_object['result']['entry'][n]['destination']['member'][0] == 'any':
        print(json_object['result']['entry'][n]['@name'])
suresh@mac:~/Documents/ghost-pro/palo-rest-api/post|⇒  python policy-use-case.py 

test-policy

As you can see above, the output contains a single rule (test-policy) which meets the criteria.

Closing up

Both Python and API are very powerful tools for data scraping and analysis. You can pretty much get any information that you want by simply writing a script instead of going through it manually via the web GUI.

Palo Alto REST API - POST Request Example
In the previous two posts, we have covered how to use Palo Alto REST API to GET/retrieve information from the firewalls. In this post, we will