Palo Alto PAN-OS SDK for Python - Working with Panorama

In this blog post, we'll explore how to use the pan-os-python library with Panorama. Working with Panorama is a bit different because of device-groups and templates. I'll show you the differences between connecting to Panorama and a regular firewall. We'll also go through some examples, like making objects and setting up rules. If you're new to the pan-os-python library, please take a look at my other blog post linked below.

Palo Alto PAN-OS SDK for Python
Let’s start learning the pan-os-python library with a simple script. This script will be our foundation, and it’s as straightforward as it gets, creating a basic firewall rule on a Palo Alto firewall.

Connecting to a Regular Firewall

In the snippet below, we're connecting to a regular Palo Alto firewall using the pan-os-python library.

from panos.firewall import Firewall
from panos.objects import AddressObject

fw_object = Firewall('FIREWALL_IP', 'USERNAME', 'PASSWORD')

new_address = AddressObject(
    name='server_1',
    value='192.168.10.10/32',
    type='ip-netmask',
    description='This is server_1 IP'
)

fw_object.add(new_address)
new_address.create()

First, we import the necessary modules. The Firewall class lets us establish a connection to the firewall by providing its IP, username, and password.

We then define a new address object with the AddressObject class, setting its name, IP address, type, and a brief description. After establishing the connection with fw_object, we add our new address object and instruct it to be created on the firewall.

Now that we've covered this, in the next section, we'll dive into how to create this same object in Panorama.

Connecting to Panorama

In this section, the primary focus is on connecting to Panorama instead of a standalone firewall.

Address Objects

from panos.panorama import Panorama, DeviceGroup
from panos.objects import AddressObject

panorama_object = Panorama('PANORAMA_IP', 'USERNAME', 'PASSWORD' )

dg_object = DeviceGroup("test_dg")
panorama_object.add(dg_object)

new_address = AddressObject(
    name='server_1',
    value='192.168.10.10/32',
    type='ip-netmask',
    description='This is server_1 IP'
)

dg_object.add(new_address)
new_address.create()

Here's what's different

  • Connection to Panorama - The first notable change is the use of the Panorama class for establishing a connection. We pass in the Panorama IP, username, and password to get our panorama_object.
  • Working with Device Groups - A key difference when dealing with Panorama is the introduction of the DeviceGroup. Panorama uses device groups to manage similar firewalls as one logical unit. Our script defines a device group named 'test_dg' and adds it to our panorama_object.
  • Adding Address to Device Group -Instead of adding the new address directly to the firewall, we're adding it to our device group (dg_object). By doing this, the new address object will be available to all firewalls under this device group in Panorama.
💡
Please note that if you add the new_address object directly to the panorama_object instead of the dg_object, the Address Object will be created under the Shared device group.

Security Policy

Here is another example of creating a Security Rule in Panorama under a specific device-group.

from panos.panorama import Panorama, DeviceGroup
from panos.objects import AddressObject
from panos.policies import PreRulebase, SecurityRule

panorama_object = Panorama('PANORAMA_IP', 'USERNAME', 'PASSWORD' )

dg_object = DeviceGroup("test_dg")
panorama_object.add(dg_object)

rules_object = dg_object.add(PreRulebase())
new_rule_object = SecurityRule(
    name='Allow DNS',
    fromzone=['any'],
    tozone=['any'],
    source=['any'],
    destination=['8.8.8.8'],
    application=['dns'],
    service=['application-default'],
    action='allow'
)

rules_object.add(new_rule_object)

response = new_rule_object.create()
print(response)

You might be thinking how do I know which Classes to use and how to associate them with each other? Well, it takes a bit of time to figure out which Classes to use. I typically start from the Panorama Class and then make my way through the hierarchy.

This hierarchy and relationship between these Classes help ensure that when you're pushing configurations from your Python script, they are applied in the correct order and to the right set of devices as it would if you were manually configuring them through the Panorama GUI.

Templates

In the provided code, we start by importing the necessary classes. Just like with the device-group, we first create an instance of the Panorama Class with the Panorama's IP, username, and password.

from panos.panorama import Panorama, Template
from panos import network

panorama_object = Panorama('PANORAMA_IP', 'USERNAME', 'PASSWORD')

tp_object = Template("lab")
panorama_object.add(tp_object)

new_zone = network.Zone(
    name="test_zone",
    mode="layer3"
)

tp_object.add(new_zone)
new_zone.create()

The next step is the creation of a template. We initiate an instance of the Template Class and name it tp_object. After defining the template, we associate it with the Panorama instance by using the add() method.

Now, once the template is associated with Panorama, we can begin to define objects that fall under this template. In this example, a new zone named 'test_zone' with mode set to 'layer3' is created using the network.Zone class.

Finally, this zone is associated with the template by using the add() method on the template object. The create() method is then used to send the configuration to Panorama.

Please note that even though I only used two parameters while creating the new zone (name and mode), there are other parameters available as shown here.

You can find more details by navigating to the pan-os-python Library guide here.

Python OOP Inheritance

In Python, inheritance is a core principle of object-oriented programming that allows one class to inherit attributes and methods from another class. This facilitates the creation of a new class based on an existing class, encouraging code reusability and establishing a relationship between the parent (base) and child (derived) class.

In the context of the pan-os-python library, when you associate a device-group with Panorama, you are setting up a hierarchical relationship. Here's what happens in a more detailed manner.

  1. Initialization - When you create an instance of the DeviceGroup class, you're essentially defining a logical representation of a device-group in your Python script.
  2. Association - When you add this device-group to the Panorama object, you're establishing a relationship that signifies the device-group exists within that Panorama object.
  3. Meaning in the Context of the Library - By setting this relationship, you are effectively telling the library, 'All subsequent operations or configurations that I perform on this device-group will be in the context of this specific Panorama instance'. This ensures that when configurations are pushed or pulled, they are done so for this device-group on this specific Panorama.
  4. No Actual API Calls Yet - At this point, no API calls to Panorama have been made. The library is merely organizing objects in a hierarchical manner locally within your script.
  5. Actual Configuration Push - Once you've set up the objects and relationships in your script, you'll use specific methods like .create(), .update(), .apply() etc., to actually make API calls to Panorama to perform configuration tasks. The library will use the relationships you've defined to determine the correct API endpoints and structure the API requests appropriately.

In summary, inheritance helps structure your configuration in a way that mirrors how Panorama organizes configurations. It ensures that when you're ready to push configurations or perform other operations, the library knows how to correctly structure the API requests based on the relationships you've set up in your script.

References

Palo Alto Networks PAN-OS SDK for Python — Palo Alto Networks PAN-OS SDK for Python 1.11.0 documentation