confgraph

Palo Alto PAN-OS Parser Support Documentation

Overview

The PAN-OS parser (confgraph.parsers.panos_parser.PANOSParser) parses Palo Alto Networks PAN-OS device configurations in XML format. Unlike all other parsers, it does not use CiscoConfParse — PAN-OS configurations are XML documents, not line-oriented text. Instead it uses a lightweight XML navigation helper (confgraph.parsers.panos_xml) built on Python’s standard xml.etree.ElementTree.

Two document layouts are read (CCR-0034 / CCR-0041): a local firewall export (devices/entry/{deviceconfig,network,vsys/entry}) and a Panorama export (device-group pre/post-rulebase, shared rulebase, and network/vsys config nested inside template entries). Layout is decided exactly once by panos_xml.detect_layout, which hands every parse method a layout-neutral view (device / vsys / policy scopes) so no method ever asks “am I Panorama?”. A document in neither known layout raises ParseError rather than returning an empty model — “this firewall has no rules” and “this firewall’s rules are in a place we don’t read” must not look the same.

Class: confgraph.parsers.panos_parser.PANOSParser Inherits from: BaseParser XML helper: confgraph.parsers.panos_xml OSType: OSType.PANOS (“panos”)


Key Syntax Differences from IOS

Feature IOS PAN-OS
Config format Line-by-line, indentation-based XML document
VRF equivalent vrf definition NAME <virtual-router> under <network>
Interface IP ip address X MASK on interface <layer3><ip><entry name="X/LEN"/>
Subinterfaces Named subinterfaces (Gi0/0.100) Units under <layer3><units><entry name="...">
BGP router bgp ASN <protocol><bgp> inside a virtual-router
BGP peer-groups neighbor IP peer-group NAME <peer-group> containing <peer> entries
BGP update-source neighbor IP update-source INTF <local-address><interface> per peer
OSPF router ospf PROC <protocol><ospf> inside a virtual-router
Static routes ip route PREFIX MASK NEXTHOP <routing-table><ip><static-route>
ACLs ip access-list NAME <rulebase><security><rules> — zone-based rules
NAT ip nat inside source <rulebase><nat><rules> — source/destination translation
IPsec crypto map / crypto isakmp policy <ike><gateway> + <tunnel><ipsec>
Security zones Not applicable <zone> entries in vsys — fundamental segmentation unit

Configuration Syntax Support

1. Virtual Routers (VRF equivalent)

XML structure:

<network>
  <virtual-router>
    <entry name="default">
      <interface>
        <member>ethernet1/1</member>
        <member>loopback.1</member>
      </interface>
    </entry>
  </virtual-router>
</network>

PAN-OS-Specific Differences:

Supported Attributes:

Parsing Status: ✅ Implemented — parse_vrfs() handles <network><virtual-router> entries across all device scopes


2. Interface Configuration

XML structure:

<interface>
  <ethernet>
    <entry name="ethernet1/1">
      <layer3>
        <ip><entry name="203.0.113.2/30"/></ip>
        <mtu>1500</mtu>
      </layer3>
      <comment>ISP Uplink</comment>
    </entry>
  </ethernet>
  <loopback>
    <units>
      <entry name="loopback.1">
        <ip><entry name="10.255.255.1/32"/></ip>
      </entry>
    </units>
  </loopback>
  <tunnel>
    <units>
      <entry name="tunnel.1">
        <ip><entry name="10.100.1.1/30"/></ip>
        <comment>IPsec tunnel to Branch-A</comment>
      </entry>
    </units>
  </tunnel>
</interface>

PAN-OS-Specific Differences:

Supported Attributes:

Tunnel underlay chain (CCR-0116): A tunnel.N interface rides an IPSec tunnel (bound via <tunnel-interface>) whose IKE gateway’s local-address/interface is the physical egress the tunnel actually depends on. parse_interfaces() resolves that chain onto the tunnel’s InterfaceConfig:

Field Source
tunnel_ike_gateway IKE gateway named under the IPSec tunnel’s <auto-key><ike-gateway><entry name="…"/>
tunnel_protection_profile <auto-key><ipsec-crypto-profile> (Phase-2 / IPSec profile)
tunnel_underlay_interface the bound IKE gateway’s network/ike/gateway/.../local-address/interface (physical egress)
tunnel_ike_crypto_profile the bound IKE gateway’s Phase-1 profile, read nested under protocol/ikev{1,2}/ike-crypto-profile

Only <auto-key> IPSec tunnels bind a gateway; manual-key and GlobalProtect-satellite tunnels have no IKE gateway and are skipped so no egress edge is invented. Binding degrades gracefully: a tunnel referencing a missing gateway still records what is known and simply leaves tunnel_underlay_interface unset (no tunnel→egress edge emitted).

Interface type classification:

Name pattern InterfaceType
loopback.*, lo.* LOOPBACK
tunnel.* TUNNEL
ae*, bond* PORTCHANNEL
vlan*, vl* VLAN
mgmt*, management* MANAGEMENT
All others (ethernet*) PHYSICAL

Parsing Status: ✅ Implemented — parse_interfaces() handles ethernet, loopback, tunnel, and aggregate-ethernet interface types with zone/VR cross-referencing


3. BGP Configuration

XML structure:

<virtual-router>
  <entry name="default">
    <protocol>
      <bgp>
        <enable>yes</enable>
        <router-id>10.255.255.1</router-id>
        <local-as>65001</local-as>
        <peer-group>
          <entry name="UPSTREAM-ISP">
            <peer>
              <entry name="ISP-A-Peer">
                <enable>yes</enable>
                <peer-address><ip>203.0.113.1</ip></peer-address>
                <connection-options>
                  <remote-as>64512</remote-as>
                  <keep-alive-interval>30</keep-alive-interval>
                </connection-options>
                <local-address>
                  <ip>203.0.113.2</ip>
                  <interface>ethernet1/1</interface>
                </local-address>
              </entry>
            </peer>
          </entry>

          <!-- BGP over IPsec tunnel -->
          <entry name="BRANCH-VPN">
            <peer>
              <entry name="Branch-A">
                <peer-address><ip>10.100.1.2</ip></peer-address>
                <connection-options><remote-as>65101</remote-as></connection-options>
                <!-- update-source = tunnel interface (IPsec) -->
                <local-address>
                  <ip>10.100.1.1</ip>
                  <interface>tunnel.1</interface>
                </local-address>
              </entry>
            </peer>
          </entry>
        </peer-group>
      </bgp>
    </protocol>
  </entry>
</virtual-router>

PAN-OS-Specific Differences:

Supported Attributes:

BGP over IPsec tunnels: When <local-address><interface> references a tunnel interface, the update-source edge plus the CCR-0116 tunnel-underlay binding chain the full path:

bgp:65001 ──[update_source]──► iface:tunnel.1 ──[tunnel_underlay]──► iface:ethernet1/4
                                     └──────────[tunnel_ike_gateway]──► crypto

This makes the full BGP → tunnel → physical egress → IPsec dependency chain visible.

Parsing Status: ✅ Implemented — parse_bgp() handles <protocol><bgp> per virtual-router with peer-group/peer hierarchy, update_source capture, auth-profile resolution, redist-profile resolution, and routing-options


3b. BGP Import/Export Policy → RouteMapConfig

XML structure:

<bgp>
  <policy>
    <import>
      <rules>
        <entry name="PREFER-BRANCH">
          <enable>yes</enable>
          <used-by><member>BRANCH-VPN</member></used-by>
          <match><address-prefix><entry name="10.100.0.0/16"/></address-prefix></match>
          <action><allow><update><local-preference>200</local-preference></update></allow></action>
        </entry>
      </rules>
    </import>
    <export></export>
  </policy>
</bgp>

PAN-OS-Specific Differences:

Parsing Status: ✅ Implemented — parse_route_maps() maps <bgp><policy><import|export><rules> to RouteMapConfig. (parse_prefix_lists() returns [] — PAN-OS has no named prefix-list object; policy prefixes are inline on the policy node.)


4. OSPF Configuration

XML structure:

<protocol>
  <ospf>
    <enable>yes</enable>
    <router-id>10.255.255.1</router-id>
    <area>
      <entry name="0.0.0.0">
        <interface>
          <entry name="ethernet1/5">
            <enable>yes</enable>
            <passive>no</passive>
            <metric>10</metric>
          </entry>
          <entry name="loopback.1">
            <enable>yes</enable>
            <passive>yes</passive>
          </entry>
        </interface>
      </entry>
    </area>
    <export-rules>
      <entry name="connected"/>
      <entry name="static"/>
    </export-rules>
  </ospf>
</protocol>

PAN-OS-Specific Differences:

Supported Attributes:

Parsing Status: ✅ Implemented — parse_ospf() handles <protocol><ospf> per virtual-router with area types, per-interface settings, and redist-profile resolution


5. Static Routes

XML structure:

<routing-table>
  <ip>
    <static-route>
      <entry name="default-route">
        <destination>0.0.0.0/0</destination>
        <nexthop><ip-address>203.0.113.1</ip-address></nexthop>
        <metric>10</metric>
      </entry>
      <entry name="tunnel1-remote">
        <destination>10.100.1.2/32</destination>
        <nexthop><ip-address>10.100.1.2</ip-address></nexthop>
        <interface>tunnel.1</interface>
        <metric>1</metric>
      </entry>
    </static-route>
  </ip>
</routing-table>

Supported Attributes:

Parsing Status: ✅ Implemented — parse_static_routes() handles <routing-table><ip><static-route> per virtual-router, separating admin-dist from metric


6. Security Policies → ACLConfig

XML structure:

<rulebase>
  <security>
    <rules>
      <entry name="trust-to-internet">
        <from><member>trust</member></from>
        <to><member>untrust</member></to>
        <source><member>10.10.0.0/24</member></source>
        <destination><member>any</member></destination>
        <application>
          <member>web-browsing</member>
          <member>ssl</member>
        </application>
        <action>allow</action>
      </entry>
      <entry name="deny-all">
        <from><member>any</member></from>
        <to><member>any</member></to>
        <source><member>any</member></source>
        <destination><member>any</member></destination>
        <application><member>any</member></application>
        <action>deny</action>
      </entry>
    </rules>
  </security>
</rulebase>

PAN-OS-Specific Differences:

Supported Attributes:

Source-NAT address sets: Each source-NAT rule additionally materializes an ACL of its own (named nat-source-{rule}) holding the address set that rule translates, so NATDynamicEntry.acl (see §7) resolves instead of dangling — the same rulebase→ACLConfig mapping already applied to security rules (CCR-0035 #7).

Parsing Status: ✅ Implemented — parse_acls() maps <rulebase><security><rules> to ACLConfig per policy scope, plus one source-NAT ACL per source-translating NAT rule


7. NAT Policies → NATConfig

XML structure:

<rulebase>
  <nat>
    <rules>
      <!-- Source NAT (PAT via interface) -->
      <entry name="trust-snat-to-internet">
        <source-translation>
          <dynamic-ip-and-port>
            <interface-address>
              <interface>ethernet1/1</interface>
            </interface-address>
          </dynamic-ip-and-port>
        </source-translation>
      </entry>

      <!-- Destination NAT (DNAT to web server) -->
      <entry name="dnat-web-server">
        <destination><member>203.0.113.2</member></destination>
        <destination-translation>
          <translated-address>172.16.10.10</translated-address>
          <translated-port>443</translated-port>
        </destination-translation>
      </entry>
    </rules>
  </nat>
</rulebase>

PAN-OS-Specific Differences:

Supported Attributes:

Parsing Status: ✅ Implemented — parse_nat() captures destination and static-source translations as NATStaticEntry and dynamic/PAT source translations as NATDynamicEntry


8. IPsec / IKE → CryptoConfig

XML structure:

<ike>
  <crypto-profiles>
    <ike-crypto-profiles>
      <entry name="IKEv2-AES256-SHA256-DH14">
        <encryption><member>aes-256-cbc</member></encryption>
        <hash><member>sha256</member></hash>
        <dh-group><member>group14</member></dh-group>
        <lifetime><hours>8</hours></lifetime>
      </entry>
    </ike-crypto-profiles>
    <ipsec-crypto-profiles>
      <entry name="IPSec-AES256-SHA256">
        <esp>
          <encryption><member>aes-256-cbc</member></encryption>
          <authentication><member>sha256</member></authentication>
        </esp>
      </entry>
    </ipsec-crypto-profiles>
  </crypto-profiles>

  <gateway>
    <entry name="GW-Branch-A">
      <peer-address><ip>198.51.100.10</ip></peer-address>
      <local-address>
        <interface>ethernet1/4</interface>
      </local-address>
      <ike-crypto-profile>IKEv2-AES256-SHA256-DH14</ike-crypto-profile>
    </entry>
  </gateway>
</ike>

<tunnel>
  <ipsec>
    <entry name="IPSEC-Branch-A">
      <auto-key>
        <ike-gateway><entry name="GW-Branch-A"/></ike-gateway>
        <ipsec-crypto-profile>IPSec-AES256-SHA256</ipsec-crypto-profile>
      </auto-key>
      <tunnel-interface>tunnel.1</tunnel-interface>
    </entry>
  </ipsec>
</tunnel>

PAN-OS-Specific Differences:

Supported Attributes:

Named objects (CCR-0139): crypto.ike_gateways and crypto.ike_crypto_profiles carry the two middle links of the tunnel chain by name, alongside the anonymous forms above (which are unchanged — this is additive, and their consumers were untouched). The names are what make a reference resolvable: without them, deleting an IKE crypto profile object a gateway still points at is invisible, and a broken link cannot be named.

Named object Fields
IKEGateway name, egress_interface (local-address/interface), peer_address (peer-address/ip), ike_version (protocol/version), ike_crypto_profile
IKECryptoProfile name, encryption, hash, group, lifetime

Both are PAN-OS-only: the IOS family has no named IKE gateways or IKE crypto profiles (its ISAKMP policies are keyed by priority), so its parsers leave both lists empty. IKECryptoProfile deliberately omits IKEv1Policy’s priority (PAN-OS has no policy priority — the flattened form synthesizes one from walk order) and authentication (never read). Entries with no @name are skipped from the named lists — nothing can reference them — and still produce their anonymous rows.

_ike_gateways() is the single reader of gateway attributes, feeding both the named list and the tunnel-interface binding, so a tunnel’s tunnel_underlay_interface / tunnel_ike_crypto_profile are always copies of the bound gateway’s own egress_interface / ike_crypto_profile (the copy invariant stated on those model fields).

Known gap — profile lifetime units: a device emits an IKE crypto profile’s lifetime as exactly one of lifetime/{seconds,minutes,hours,days}; parse_crypto() reads only hours, so the other three parse to lifetime = None (both the named object and its flattened twin). Recorded in syntax-corpus/panos/ipsec.yaml: ike-crypto-profile.

IKE crypto profile location (CCR-0116): A device emits the gateway’s Phase-1 profile nested under the negotiated IKE versionprotocol/ikev2/ike-crypto-profile or protocol/ikev1/ike-crypto-profile — not as a flat child of the gateway. parse_crypto() reads the nested shape first (falling back to a flat <ike-crypto-profile> only as lenient back-compat for hand-written configs); the previous flat-only read returned nothing on a real export. This is the same reader parse_interfaces() uses for the tunnel underlay chain, so the crypto map and the tunnel binding never disagree.

Parsing Status: ✅ Implemented — parse_crypto() handles <ike><crypto-profiles>, <ike><gateway> (nested Phase-1 profile), and <tunnel><ipsec> blocks


9. Security Zones → PANOSZoneConfig

XML structure:

<vsys>
  <entry name="vsys1">
    <zone>
      <entry name="untrust">
        <network>
          <layer3>
            <member>ethernet1/1</member>
            <member>ethernet1/4</member>
          </layer3>
          <zone-protection-profile>Zone-Protect-Strict</zone-protection-profile>
        </network>
        <log-setting>default</log-setting>
      </entry>
      <entry name="vpn-tunnels">
        <network>
          <tunnel>
            <member>tunnel.1</member>
            <member>tunnel.2</member>
          </tunnel>
        </network>
      </entry>
    </zone>
  </entry>
</vsys>

PAN-OS-Specific Differences:

Supported Attributes:

Parsing Status: ✅ Implemented — parse_zones() handles all zone types across all vsys entries; zone membership also populates InterfaceConfig.zone


Graph Visualization

PAN-OS configs produce a graph with the following node types:

Node type Color Represents
interface Blue Ethernet, loopback, tunnel, AE interfaces
vrf Blue Virtual routers
bgp_instance Green BGP process per virtual-router
ospf_instance Green OSPF process per virtual-router
static_route Green Static routing entries
route_map Green BGP import/export policy rules (policy nodes)
acl Amber Security policy rulebase (zone-based) + source-NAT address sets
nat Red NAT policy (DNAT + source/PAT entries)
crypto Red IKE/IPsec configuration
zone Red Security zones

Key dependency chains visible in the graph:

Sidebar clusters available: BGP, OSPF, NAT, Crypto/VPN, Zones


Parser Architecture

Unlike IOS-style parsers, PAN-OS uses a layered approach:

Config text (XML)
    │
    ▼
panos_xml.parse_panos_xml()       Strip namespace declarations, ElementTree.fromstring()
    ├── entries(parent, path)     findall("{path}/entry")
    ├── text_val(el, path)        find(path).text.strip()
    ├── members(el, path)         findall("{path}/member")
    └── raw_xml(el)               indented tostring() for raw_config
    │
    ▼
panos_xml.detect_layout()         Classify the document EXACTLY ONCE:
    ├── local firewall  → devices/entry/{deviceconfig,network,vsys}
    ├── Panorama        → device-group pre/post-rulebase + shared + template/config
    └── neither         → raise UnrecognizedPANOSLayout → ParseError (no silent-empty model)
    │                     Returns a layout-neutral PANOSLayout view:
    │                       • device scopes  (own deviceconfig/network/vsys)
    │                       • vsys scopes    (own zone)
    │                       • policy scopes  (rulebase chains in evaluation order)
    ▼
PANOSParser parse methods         Consume the neutral scopes — never ask "am I Panorama?"
    │                             Panorama device-group hierarchy (parent-dg) resolved
    │                             from /config/readonly; template-stacks NOT read
    ▼
ParsedConfig                      Standard model used by all OS types

Panorama specifics (_panorama_layout / _panorama_policies): each device-group’s effective rulebase chain is resolved to shared-pre → ancestor-DG-pre → … → own-DG-pre → own-DG-post → … → shared-post, using the parent-dg hierarchy emitted at /config/readonly/.... Template-stacks are deliberately not read (their config is assembled from member templates by an unstated priority); a template-stack-only document is an unrecognized layout and raises, rather than silently returning nothing.


Implemented Methods Summary

Method What it handles
_extract_hostname() <deviceconfig><system><hostname> across device scopes
_collect_unrecognized_blocks() Returns [] — CiscoConfParse not used
parse_vrfs() <network><virtual-router> entries + member interface list
parse_interfaces() Ethernet, loopback, tunnel, AE interfaces with zone/VR cross-referencing and the CCR-0116 tunnel-underlay binding
parse_bgp() <protocol><bgp> per virtual-router: peer-group/peer hierarchy, timers, multihop, auth-profile, redist-profiles, routing-options
parse_route_maps() <bgp><policy><import\|export><rules>RouteMapConfig policy nodes
parse_prefix_lists() Returns [] — PAN-OS has no named prefix-list; policy prefixes are inline
parse_ospf() <protocol><ospf> per virtual-router: area types, per-interface settings, redist-profiles
parse_static_routes() <routing-table><ip><static-route> per virtual-router (admin-dist ≠ metric)
parse_acls() <rulebase><security><rules> per policy scope + source-NAT address-set ACLs
parse_nat() <rulebase><nat><rules> — DNAT + static/dynamic/PAT source NAT
parse_crypto() IKE crypto profiles, IPsec profiles, IKE gateways (nested Phase-1 profile)
parse_zones() <vsys><zone> entries across all virtual systems

Parser Limitations

  1. IPv6 routing protocols — IPv6 static routes and OSPFv3 are not parsed. (BGP is IPv4-unicast; IPv6 redist-profiles are read for protocol names but there are no IPv6 BGP peerings.)
  2. Template-stacks — Panorama template config is read, but template-stack entries are not: a stack assembles its config from member templates by an unstated priority, so it is not resolved. A template-stack-only document is treated as an unrecognized layout and raises (rather than silently returning an empty model).
  3. Application-ID (App-ID) semantics — Security policy ACL entries capture application names as text in the remark field only; App-ID object definitions are not resolved.
  4. Address objects / address groups — Named address objects and groups referenced in security/NAT rules are not resolved to IP addresses (they are kept verbatim in the remark / source-NAT ACL).
  5. Service objects — Named service objects (port definitions) are not resolved.
  6. GlobalProtect VPN — Not parsed (GlobalProtect-satellite IPSec tunnels are recognized only insofar as they are skipped by the tunnel-underlay binding).
  7. Decryption policies — Not parsed.
  8. High Availability (HA) — HA configuration is not parsed.

Testing and Validation

Sample Configuration: samples/panos_sample.xml

Validated output (confgraph info samples/panos_sample.xml --os panos):

Hostname : pa-edge-fw01
OS       : panos

Interfaces         8
VRFs               1
BGP instances      1
OSPF instances     1
ACLs               2
Static routes      6

(ACLs = 2: the vsys security-policy-vsys1 rulebase plus one materialized nat-source-{rule} address-set ACL for the sample’s source-NAT rule.)

Auto-detection signals (used when --os is not provided):

Signal Example
<config version= PAN-OS XML config header
<devices> Top-level devices block
<vsys> Virtual system block
<rulebase> Security/NAT rulebase
<virtual-router> Network virtual-router block

Quick Reference

from confgraph.parsers.panos_parser import PANOSParser

parser = PANOSParser(config_text)
parsed = parser.parse()
# os_type = OSType.PANOS  # "panos"
confgraph info samples/panos_sample.xml --os panos
confgraph map  samples/panos_sample.xml --os panos --lint

Last Updated: 2026-07-29 Parser Version: 1.0.0