Using the Palo Alto XML API

Palo Alto version 9 and above has a nice REST-API. For those still on 8, only the older XML-API is available. Here’s a quick rundown on how to use it.

Setup accounts and roles. By default, users with an API key will have full access to the XML API. Here’s a specifically restrictive role:

Once roles are configured, each user or account should obtain a separate API key. This can be done with a simple curl request:

curl -k -X GET \

Look for the API key in the XML output between they <key></key> tags.

Download pan-python and unpack it:

tar -xzvf pan-python-0.16.0.tar.gz

Note: this can also be installed as a Python package with pip3 install pan-python. But it requires urllib2, which was replaced with urllib3 in Python3.

Give it a spin:

./pan-python-0.16.0/bin/ -h -K "LUFRPT1ZTWcxL0F6RngvNkl2eFdDeS9JMjhDUUlGbTg9ZWhlWEtjUFF3UVU1OEViZlJHWDloLzdvL0s4Zm1kU0MvZ1dMTFpnc2xZcz0=" -x -o "<show><routing><route></route></routing></show>"

This will return XML version of the “show routing route” CLI command.


Giving read-only access on Cisco IOS-XE with RADIUS authentication

Had a simple but time-consuming problem today.  Our Cisco IOS-XE 16.12 routers authenticate to AD via RADIUS to Microsoft NPS, with certain AD group(s) having admin privileges.  On the router side, configuration looks like this, where is the NPS server:

aaa group server radius MyRADIUS
 server-private auth-port 1812 acct-port 1813 key 0 abcd1234
 ip vrf forwarding Mgmt-intf
aaa new-model
aaa session-id common
aaa authentication login default local group MyRADIUS
aaa authentication enable default none
aaa authorization config-commands
aaa authorization exec default local group MyRADIUS if-authenticated

In NPS, I have a policy to match the appropriate Windows Group with Authentication Type = PAP and NAS Port Type = Virtual.  In the Settings tab, I then have this Vendor Specific RADIUS Attribute:

Name: Cisco-AV-Pair
Vendor: Cisco
Value: priv-lvl=15

This allows users in this group to SSH to any router and immediately have privilege level 15, which gives them full admin access.

Now and I needed to give a certain AD group read-only access to view running-configuration.  So I create a new policy matching to that AD group, and in the RADIUS attributes, under Vendor Specific, I add this one:

Name: Cisco-AV-Pair
Vendor: Cisco
Value: priv-lvl=7

The test account could then SSH to the router and verify privilege level was 7:

Router#show priv
Current privilege level is 7

I then downgraded privileges on each router so that only level 3 was required to view running-config:

privilege exec level 3 show running-config view full
privilege exec level 3 show running-config view
privilege exec level 3 show running-config
privilege exec level 3 show

But, when doing “show running-config”, they would just get a nothing back in return.  As it turns out I needed one more step; lowering the privilege for viewing files on the router

file privilege 3

Now it works:

Router#show running-config view full
Building configuration...

Current configuration : 15124 bytes
! Last configuration change at 15:39:15 UTC Tue Mar 17 2020 by admin
! NVRAM config last updated at 15:39:21 UTC Tue Mar 17 2020 by admin
version 16.12
service timestamps debug datetime msec
service timestamps log datetime localtime show-timezone
service password-encryption
no service dhcp
service call-home


A very common mistake when parsing the HTTP X-Forward-For header

Let’s say you have a web server behind a load balancer that acts as a reverse proxy.  Since the load balancer is likely changing the source IP of the packets with its own IP address, it stamps the client’s IP address in the X-Forwarded-For header and then passes it along to the backend server.

Assuming the web server has been configured to log this header instead of client IP, a typical log entry will look like this:, – – [10/Mar/2020:01:15:19 +0000] “GET / HTTP/1.1” 200 3041 “-” “Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36” is the client’s IP address, and is the Load Balancer’s IP address.   Pretty simple.  One would assume that it’s always the first entry that’s the client’s IP address, right?

Well no, because there’s an edge case.  Let’s say the client is behind a proxy server that’s already stamping X-Forward-For with the client’s internal IP address.  When the load balancer receives the HTTP request, it will often pass the X-Forwarded-For header unmodified to the web server, which then logs the request like this:,, – – [10/Mar/2020:01:15:05 +0000] “GET /  HTTP/1.1” 200 5754 “-” “Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36” is the client’s true internal IP, but we don’t care about that since it’s RFC-1918 and not of any practical use.  So it’s actually the second to last entry (not necessarily the first!!!) that contains the client’s public IP address and the one that should be used for any Geo-IP functions.

Here’s some sample Python code:

#!/usr/bin/env python

import os

x_fwd_for = os.environ.get('HTTP_X_FORWARDED_FOR', '')

if ", " in x_fwd_for:
    client_ip = x_fwd_for.split(", ")[-2]
    client_ip = os.environ.get('REMOTE_ADDR', '')

If behind NGinx, a better solution is to prefer the X-Real-IP header instead:

import os

x_real_ip = os.environ.get('HTTP_X_REAL_IP', '')
x_fwd_for = os.environ.get('HTTP_X_FORWARDED_FOR', '')

if x_real_ip:
    client_ip = x_real_ip
elif x_fwd_for and ", " in x_fwd_for:
    client_ip = x_fwd_for.split(", ")[-2]
    client_ip = os.environ.get('REMOTE_ADDR', '')

Other platforms can easily be configured to stamp an X-Real-IP header as well.  For example, on an F5 BigIP LTM load balancer, this iRule will do the job:

    HTTP::header insert X-Real-IP [IP::remote_addr]

Enabling SSL (HTTPS) on Apache 2.4 Ubuntu, with a good rating from SSL Labs as a bonus

Start by installing Apache 2.4.  This will run on port 80 out of the box:

sudo su
apt install apache2
apt install apache2-doc

To use SSL/TLS/HTTPS aka port 443 as well, follow these additional steps:

Activate the SSL, socache_shmcb, and rewrite modules:

cd /etc/apache2/mods-enabled/
ln -s ../mods-available/ssl.load .
ln -s ../mods-available/socache_shmcb.load .

Optionally, activate the headers, rewrite and proxy modules, as they are often useful:

ln -s ../mods-available/headers.load .
ln -s ../mods-available/rewrite.load .
ln -s ../mods-available/cgi.load .

Copy the default ssl.conf file over and edit it:

cp /etc/apache2/mods-available/ssl.conf /etc/apache2/conf-enabled/
nano /etc/apache2/conf-enabled/ssl.conf

Near the bottom, modify these lines so that the AES-GCM protocols are preferred and only TLS 1.2 is supported

   #SSLCipherSuite HIGH:!aNULL   
   SSLHonorCipherOrder on
   SSLProtocol TLSv1.2

Then edit /etc/apache2/sites-enabled/000-default.conf so it has default virtual hosts on both port 80 and port 443:

<VirtualHost _default_:80>
   ServerName localhost
   ServerAdmin webmaster@localhost
   DocumentRoot /var/www/html
   ErrorLog ${APACHE_LOG_DIR}/error.log
   CustomLog ${APACHE_LOG_DIR}/access.log combined
<VirtualHost _default_:443>
   ServerName localhost
   ServerAdmin webmaster@localhost
   DocumentRoot /var/www/html
   ErrorLog ${APACHE_LOG_DIR}/error.log
   CustomLog ${APACHE_LOG_DIR}/access.log combined
   SSLEngine On
   SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key
   SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem

Restart Apache and you should now have service for both HTTP (port 80) and HTTPS (port 443)

apachectl configtest
Syntax OK
apachectl restart

Run the site through SSL labs and the rating should be high, other than the self-signed certificate.

Using CheckPoint Dynamic Objects to Source NAT flows

By default, the CheckPoint will usually have three dynamic objects that can be referenced in firewall and NAT policy rules

  • LocalGateway – Main interface of the CheckPoint
  • LocalGatewayExternal – External interface of the CheckPoint
  • LocalGatewayInternal – First internal interface of the CheckPoint

In a 3-Nic deployment, you may want to reference the second internal NIC, for example to source NAT traffic bound to the internal servers to the CheckPoint’s internal IP address.

To do this, you must create a custom dynamic object in SmartConsole, then manually create it on each gateway.

On the gateway, first verify the internal IP address:

[Expert@gateway]# ifconfig eth2
eth2      Link encap:Ethernet HWaddr 42:01:0A:D4:80:03 
          inet addr: Bcast: Mask:

Create the object:

[Expert@gateway]# dynamic_objects -n LocalGateway-eth2 -r -a

Verify it’s been created:

[Expert@gateway]# dynamic_objects -l

object name : LocalGateway
range 0 :

object name : LocalGatewayExternal
range 0 :

object name : LocalGatewayInternal
range 0 :

object name : LocalGateway-eth2
range 0 :

Source: skI1915 – Configuring Dynamic Objects