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 objectjson.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"
}
]
}
}
Print Rule names
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.