irom's Blog

Palo Alto firewall automation

Posted on: October 12, 2016

Palo Alto firewall operating system PANOS includes a REST API which allows to run commands and capable of providing device-level information. Palo Alto provides also free ‘Palo Alto Networks Device Framework’ (called pandevice, currently in alpha version) to interact with firewalls (as well as management server Panorama) in a way conceptually similar to interaction with the device via the GUI or CLI. In this article I am showing how to schedule commands on PaloAlto firewalls using pandevice and Jenkins which is continuous integration and delivery (CI/CD) application capable of scheduling jobs (see my post ‘Jenkins as system job scheduler’ )

First obvious automation of firewall operations is scheduling policy installation. The best practice is to make changes after working hours. In Jenkins I have two jobs scheduled:

  1. Firewall commit – to commit changes on active firewall (pandevice.fw.commit_all.py)
  2. Panorama commit – to commit changes on management server (pandevice.pano.commit_all.py)

Both jobs can be pictured in Jenkins using graphviz, see below. Panorama commit job is triggered by Firewall commit job (second job runs after first job is completed). Panorama commit is not always enough, because, for example, firewall can be connected to Active Directory servers and  ‘Group Mapping Settings’ updates have to be done there.

paloalto-jenkins-graph

Both Jenkins jobs connect using ssh to remote Linux server (called here ‘nms02’) and run scripts in python: pandevice.fw.commit_all.py and pandevice.pano.commit_all.py

paloalto-jenkins-build

Looking at first script pandevice.fw.commit_all.py you can see it is very straightforward. It imports pandevice libraries, defines commit function using firewall objects fw1 and fw2 running it on active firewall in cluster which is supposed to synchronize with passive. There is 2 min wait before displaying statuses of all job processed after commit for job execution reporting in Jenkins.

#!/usr/bin/python
from pandevice import base
from pandevice import firewall
from pandevice import panorama
from pandevice import policies
from pandevice import objects
from pandevice import network
from pandevice import device
import time
def commit(job, fw):       
        print "fw" + str(job) + " is ACTIVE"
        fw.commit()
        time.sleep(120)
        cmd = “show jobs processed”
        print fw.op(cmd, xml=True)
        return
fw1 = firewall.Firewall('ip1', 'admin', 'password1')
fw2 = firewall.Firewall('ip2', 'admin', 'password2')
#Establish an HA peer relationship between two PanDevice objects
fw1.set_ha_peers(fw2)
#Refresh which device is active using the live device
if 'active' in fw1.refresh_ha_active():
        commit(1,fw1)
elif 'active' in fw2.refresh_ha_active():
        commit(2,fw2)
else:
        print 'NO ACTIVE fw FOUND'

The function commit() does have an optional param sync=True, that will block until the commit is finished so the sleep might not be required if passing this param, but I’ve not tried it yet. Second script is even simpler because it just sends policy from Panorama management server to device group ‘PROD’ and verifies status of the job after 5 min

#!/usr/bin/python
from pandevice import base
from pandevice import firewall
from pandevice import panorama
from pandevice import policies
from pandevice import objects
from pandevice import network
from pandevice import device
import time
pano = panorama.Panorama("ip", "admin", "password")
id = pano.commit_all(devicegroup=”PROD")
time.sleep(300)
cmd = 'show jobs id "' + id + '"'
print pano.op(cmd, xml=True)

In Jenkins you can monitor the status of each job execution defining rules for analyzing job output and sending e-mail notification.

paloalto-jenkins-post-build

Rules below are an example of my error and warning reporting

$ pwd
/var/lib/jenkins/log_rules
[irek@nms01m log_rules]$ cat PaloAlto.rule
#API
ok /exit-status: 0/
# match line starting with 'error ', case-insensitive
error /(?i)error /
# more errors
error /[Ii]llegal/
 
# list of warnings here...
warning /[Ww]arning/
warning /\s[Nn]ot\s/
 
# create a quick access link to lines in the report containing 'INFO'
info /INFO/
info /No such file or directory/

I have also few other jobs defined in Jenkins:

  • export configuration every 4h
  • save configuration every 1h, then once a day (1pm) and night (1am)
  • validate Panorama policy every 2h between 8 and 22 pm

paloalto-jenkins

Save and export jobs are using REST API directly with curl and api-key generated with http(s)://hostname/api/?type=keygen&user=username&password=password

Job Command
Save hourly curl -k “https://ip/api/?type=op&cmd=<save><config><to&gt;Jenkins-Panorama</to></config></save>&key=api-key”
Save daily curl -k “https://ip/api/?type=op&cmd=<save><config><to&gt;Jenkins-Panorama-day</to></config></save>&key=api-key”
Save nightly curl -k “https://ip/api/?type=op&cmd=<save><config><to&gt;Jenkins-Panorama-night</to></config></save>&key=api-key”
Export curl -kG “https://ip/api/?type=export&category=configuration&key=api-key&#8221; > Panorama-config.xml

 

Saved configurations can be loaded back to Panorama from Setup->Load->Load named Panorama snapshot

paloalto-load

Exported configuration file is available in the home directory of user which Jenkins job is using to connect using ssh tu run ‘curl’ command

$ ls -l Panorama-config.xml

-rw-rw-r–. 1 ftpuser ftpuser 513916 Oct 11 11:38 Panorama-config.xml

Validate is performed using pandevice.pano.validate_all.py , see below:

$ cat pandevice.pano.validate_all.py
#!/usr/bin/python
from pandevice import base
from pandevice import firewall
from pandevice import panorama
from pandevice import policies
from pandevice import objects
from pandevice import network
from pandevice import device
import time
import re
pano = panorama.Panorama("ip", "admin", "password")
#print fw.op("show system info", xml=True)
#print pano.op("show system info", xml=True)
#print pano.commit_all(devicegroup="PROD")
response = pano.op('<validate><full></full></validate>', xml=True, cmd_xml=False)
#response = '<response code="19" status="success"><result><msg><line>Validate job enqueued with jobid 5052</line></msg><job>5052</job></result></response>'
#response = '<response code="19" status="success"><msg>There are no changes to validate.</msg></response>'
jobid = re.match( r'.*jobid ([0-9]*)',response, re.M|re.I)
if jobid:
    print jobid.group(1)
    time.sleep(300)
    cmd = 'show jobs id "' + jobid.group(1) + '"'
    print pano.op(cmd, xml=True)
else:
    print "There are no changes to validate."

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Twitter Updates

Blogs I Follow

%d bloggers like this: