If you’ve worked with Palo Alto firewalls, you might have noticed they don’t make it easy to get rid of unused address objects. It seems like such a basic feature should be included, right? While you could use Expedition for this, it requires setting up a separate server and learning a new tool, which might be more hassle than it’s worth.
I’ve talked before about using a simple Python script to clean up unused address objects (link below), but it was pretty basic and I didn't take many scenarios into account. Today, I want to show you an even easier more sophisticated way to handle this using Pan-OS-PHP. This tool is fantastic because you can use it directly from the command line. You don’t need to know any PHP to get started. Let’s look at how this can make managing your firewall a lot easier.
Pan-OS-PHP Intro
Pan-OS-PHP is described as a “Framework and utilities to easily manage and edit Palo Alto Networks PAN-OS devices.” This PHP-based tool is hosted on Palo Alto’s official GitHub repository. The official version was last updated in 2023. However, there’s a more recent version maintained by one of the contributors; this fork was updated just two weeks ago, as of July 2024. This active maintenance indicates that the tool continues to evolve.
I’m still getting the hang of using Pan-OS-PHP, and it took me a bit of time to get a good understanding on the basics. Whenever I ran into questions, I reached out to the maintainer on Slack. He was incredibly responsive and always willing to help, so I owe him a massive thanks for all the support he’s given me.
Pan-OS-PHP Installation
Installing Pan-OS-PHP is very straightforward if you’re already familiar with the basics of Docker. This guide assumes you have Docker installed on your machine. I’m using a MacOS for this example and have Docker already set up.
Before diving into the installation process, it’s important to know that you can use Pan-OS-PHP in a couple of different ways.
- Connect to the firewall directly - You can configure the tool to directly connect to your firewall or Panorama via the API. Using this method, the tool will remove the unused objects, but remember, you’ll still need to commit these changes manually.
- XML Configuration File - If you prefer not to connect the tool directly to your devices, you can export the configuration from your firewall, modify it with Pan-OS-PHP, and then import the updated configuration back. This method gives you more control and review over the changes.
The tool can also generate 'set' command outputs that you can execute manually in the CLI for further customisation or control. To make this work, you need to run Docker from a shared directory. This allows the Docker container to read and write files within that directory.
For this example, I just created a directory called pan_os_php
, navigated to it and ran the following single command.
➜ docker run --name panosphp --rm -v ${PWD}:/share -it swaschkut/pan-os-php:latest
root@d8f81d529e8f:/share#
root@d8f81d529e8f:/share#
root@d8f81d529e8f:/share#
--name panosphp
- This names your Docker container 'panosphp'--rm
- This option removes the container once it stops running-v ${PWD}:/share
- This mounts the current working directory (${PWD}) to the/share
directory inside the container, allowing the tool to access and modify files in your directory.-it
- This runs the container in interactive mode, keeping the session open for you to interact with.swaschkut/pan-os-php:latest
- Specifies the Docker image to use, pulling the latest version of Pan-OS-PHP maintained by swaschkut.
root@d8f81d529e8f:/share# ls
running-config.xml
As you can see above, I can access the exported configuration from inside the container.
What's Our Goal Here?
Our goal here is simple. We want to find and remove any unused address objects and address groups from the firewall or Panorama. When I say “unused,” I mean any object that’s sitting there but isn’t being referenced or used anywhere.
Let’s look at a simple example to understand how we can clean up unused address objects in the firewall. In our example, we have six address objects and two address groups. Here’s how they are used.
- app_server_1 is directly used in a security policy.
- The three DNS servers are part of two address groups. One of these groups is used in a security policy.
When we do our magic, our goal is to automatically identify and remove app_server_2
, app_server_subnet
and google_dns_servers
because they are not referenced anywhere. Of course, this is a very simplified example, but you could have 1000s of objects and 100s of rules so, doing this manually would take hours and hours.
google_dns_2
is slightly complex, and we’ll go over this in a later section to explain why it’s tricky.Let's Start the Cleanup
Now it’s time for the cleanup, and you only need a single command to start removing those unused objects.
pan-os-php in=api://10.10.0.31 type=address 'filter=(object is.unused)' actions=delete
To successfully run the cleanup, you’ll need to provide a few mandatory parameters.
- Calling
pan-os-php
- This is how you initiate the tool. Type
- Specifies the type of resource you’re working with; here, it’s 'address'.In
Determines how you want to connect. You can connect directly to the firewall using api, or work with a configuration file if preferred.Actions
- Defines what you want to do. In this scenario, we’re deleting unused objects.
While not mandatory, using the filter parameter can refine what objects the tool acts upon. In our example, filter=(object is.unused)
specifically targets objects that are marked as unused. You can explore other filters relevant to your needs by running the listfilters
command, like this.
pan-os-php type=address listfilters
As you can see from the command output, the unused address objects and the group are now gone. The output also provides clear feedback on what has been removed.
What happened to 'google_dns_2'?
As mentioned earlier, the case of google_dns_2
is a bit tricky. While this address object is technically referenced within an address group, that particular group google_dns_servers
itself isn’t referenced anywhere else. As a result, the tool initially sees google_dns_2
as having a reference and doesn’t remove it.
However, now that the address group itself has been removed during the first cleanup run, google_dns_2
no longer has any active references. If you rerun the cleanup command, the tool should now recognize that google_dns_2
is unused and remove it as well.
Recursive
Please note that there is a filter available filter=(object is.unused.recursive)
and here is the official description for it.
"is.unused.recursive
' considers an object or a group unused only if they are NOT referenced in any rules, interfaces, static routes AND if they are referenced inside a group which itself is not referenced anywhere"
But even then, the object is not being removed so, keep this in mind when if you come across similar situation.
'set' commands
If you work in a place with strict change control policies or if you are not comfortable running the commands directly via the API, you can also get the 'set' commands output. Here is the same example that uses a specific parameter to get the 'set' commands.
pan-os-php in=api://10.10.0.31 type=address 'filter=(object is.unused)' actions=delete out=out.xml outputformatset=setcommand.txt
delete address-group "google_dns_servers"
delete address "app_server_2"
delete address "app_server_subnet"
Panorama
Using Pan-OS-PHP with Panorama works in a similar way to managing individual devices, but with a key difference involving device groups. To demonstrate, I’ve copied the same set of objects, groups, and rules into Panorama. I put the addresses and groups into the ‘Shared’ device group, and the rules into a specific device group.
When running the cleanup in Panorama, you need to specify the device group from which you’re cleaning up objects. In this case, I would indicate that I’m targeting the ‘Shared’ device group. The tool will then proceed to check for references across all device groups.
pan-os-php in=api://10.10.0.27 type=address location=shared 'filter=(object is.unused)' actions=delete