pan-os-ansible Administrator & Developer Guide

Generated April 2026 from PaloAltoNetworks/pan-os-ansible at 0ea91f8

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

Unofficial, AI-generated guide for a Palo Alto Networks open-source repository; not reviewed by Palo Alto Networks.

Note

This guide is the source-based administrator and developer reference for paloaltonetworks.panos, the official PAN-OS Ansible Collection. It is built from the in-tree sources at commit 0ea91f8 (collection version 3.3.0) and covers architecture, configuration reference, scaling, operations, performance, security, internals, and integration. Pair it with the User Guide for usage and recipes.

The collection is a thin Ansible facade over the pan-os-python SDK and the PAN-OS XML API. It contains 118 modules in plugins/modules/, one shared ConnectionHelper in plugins/module_utils/panos.py, an httpapi plugin at plugins/httpapi/panos.py, and an Event-Driven Ansible (EDA) event source under extensions/eda/. There is no on-firewall agent — everything talks to PAN-OS over its XML API on TCP/443.

1. Architecture Overview

Where the collection sits

+-------------------+        +----------------------+         +----------------+
| Ansible Control   |        | paloaltonetworks.    |         | pan-os-python  |
| Node (your CI,    |        | panos collection     |         | SDK (1.12.4+)  |
| AAP, or laptop)   |--------|  plugins/modules/    |---------|   PanDevice    |
|                   |        |  plugins/httpapi/    |         |   Firewall     |
| ansible-playbook  |        |  plugins/module_utils|         |   Panorama     |
| (Python 3.10+)    |        | (ConnectionHelper)   |         +----------------+
+-------------------+        +----------------------+                 |
                                                                      | XML over HTTPS
                                                                      v
                                                            +-------------------+
                                                            |  PAN-OS XML API   |
                                                            |  (TCP/443)        |
                                                            |   /api/?type=...  |
                                                            +-------------------+
                                                                      |
                                                                      v
                                                            +-------------------+
                                                            | NGFW / Panorama   |
                                                            | candidate config  |
                                                            | running config    |
                                                            +-------------------+

The control node runs ansible-playbook, which loads the paloaltonetworks.panos collection and executes one of the 118 modules. Each module imports pan-os-python (import panos) and uses ConnectionHelper.get_pandevice_parent() to construct the SDK object tree. The SDK then makes XML API calls to the firewall over HTTPS. The firewall mutates its candidate configuration; nothing is active on the firewall until a commit module is invoked.

Component inventory

Component Path Role
118 task modules plugins/modules/panos_*.py One module per PAN-OS resource — the public surface
Shared helper plugins/module_utils/panos.py ConnectionHelper, get_connection(), cmd_xml(), idempotency engine
HTTP API plugin plugins/httpapi/panos.py Inventory-driven httpapi connection; raw XML API client
Doc fragments plugins/doc_fragments/fragments.py 16 reusable doc fragments (provider, vsys, device_group, state, …)
EDA event source extensions/eda/plugins/event_source/logs.py Stream PAN-OS logs to EDA rulebooks
Sample rulebook extensions/eda/rulebooks/decryption.yaml Reference EDA rulebook
Integration tests tests/integration/firewall/, tests/integration/panorama/ Live-firewall idempotency tests
Unit tests tests/unit/httpapi/, tests/unit/plugins/module_utils/ httpapi plugin and module_utils unit coverage
Sanity-test exclusions tests/sanity/ignore-2.16.txt … ignore-2.20.txt Per-Ansible-version sanity-test allowlists
Galaxy metadata galaxy.yml Collection name, version, dependencies, tags
Runtime routing meta/runtime.yml Required Ansible version, deprecation redirects

Technology stack

Layer Component Version
Ansible Core The runtime >=2.16.0 per meta/runtime.yml
Python Control-node interpreter >=3.10 per requirements.txt
PAN-OS SDK pan-os-python (panos) >=1.12.4,<2.0
Upgrade Assurance panos-upgrade-assurance >=2.0.0,<3.0
XML/HTTP xmltodict, aiohttp, dpath Pinned in requirements.txt
Distribution Galaxy + GitHub releases Versioned via semantic-release (see .releaserc.json)
Test framework pytest, molecule-style integration playbooks In tests/

Note

The SDK rename history matters. pandevice was the original SDK name; it became pan-os-python (importable as panos) when 1.0 shipped. Every module in this collection has a fallback try/except ImportError chain that imports from the new name first and falls back to pandevice for backwards compat. New code should target panos only.

Data flow for a single task

ansible-playbook task                 plugins/modules/panos_address_object.py
        |                                        |
        | invokes                                |
        v                                        v
+----------------------+              +------------------------+
| AnsibleModule init   |              | get_connection(...)    |
| with argument_spec   |------------->| -> ConnectionHelper    |
+----------------------+              +------------------------+
                                                |
                                                | get_pandevice_parent()
                                                v
                                      +------------------------+
                                      | PanDevice.create_      |
                                      | from_device(provider)  |
                                      | -> Firewall|Panorama   |
                                      +------------------------+
                                                |
                                                | helper.process(module)
                                                v
                                      +------------------------+
                                      | sdk_cls.refresh()      |
                                      | -> compare candidate vs|
                                      | -> apply_state()       |
                                      | -> set/edit/delete XML |
                                      +------------------------+
                                                |
                                                v
                                      module.exit_json(changed=...)

Key invariants

  • Modules never commit by default. Configuration changes always land in the candidate; you must run a commit module (panos_commit_firewall, panos_commit_panorama, or panos_commit_push) to make them active. The legacy with_commit flag on some older modules is deprecated for the 4.0.0 major release.
  • One module per resource type. Address objects → panos_address_object, security rules → panos_security_rule, NAT rules → panos_nat_rule2, and so on. The 1:1 mapping makes diff/idempotency cheap.
  • All state lives on the device. No collection-side cache or DB. Every task fetches the current candidate state on demand and computes the diff.
  • The XML API is the source of truth for capability. A module's parameters are the union of what the SDK exposes and what the underlying PAN-OS XML schema accepts.

Note

Despite 118 modules, the design is non-obvious for one reason: every module is essentially a thin descriptor that hands off to ConnectionHelper.process() in plugins/module_utils/panos.py. That single 1900-line file handles connection, version checks, scope walking, idempotency, diff, gathered-filter, and XML serialisation. Read that file once and you have read 80 % of the collection.


2. System Requirements

Control node (where ansible-playbook runs)

Item Requirement
OS Any OS that runs Ansible Core 2.16+ (Linux, macOS, WSL2)
RAM 1 GB minimum; 4 GB recommended for parallel forks
Disk 200 MB for the collection + SDK
Python 3.10, 3.11, 3.12, or 3.13
Ansible Core 2.16 → 2.20 (sanity-tested per tests/sanity/ignore-2.*.txt)
Network egress TCP/443 to firewalls and Panorama (no inbound required)

Python dependencies

From requirements.txt:

aiohttp>=3.8.4 ; python_version >= "3.10" and python_version < "4.0"
dpath>=2.1.5 ; python_version >= "3.10" and python_version < "4.0"
pan-os-python>=1.12.4,<2.0 ; python_version >= "3.10" and python_version < "4.0"
panos-upgrade-assurance>=2.0.0,<3.0 ; python_version >= "3.10" and python_version < "4.0"
xmltodict>=0.12.0 ; python_version >= "3.10" and python_version < "4.0"

Important

Issues #648, #655 — the upper bounds in requirements.txt are tighter than strictly necessary on some non-PANW deps. If you hit a pip resolver conflict, you can usually relax aiohttp, dpath, or xmltodict upper bounds. Do not relax pan-os-python<2.0 — the collection uses 1.x APIs.

PAN-OS device

Item Requirement
PAN-OS version 8.0+ (per-module checks declare more recent minimums)
Management plane Reachable on TCP/443 from the control node
Account Admin role with XML API permission, or pre-generated API key
TLS Self-signed cert OK; trust it on the control node or set validate_certs: false

Panorama device (when applicable)

Same as a firewall, plus:

  • A device_group named scope for the objects/policies you'll manage.
  • A template and/or template_stack named scope for network/device config.
  • Managed firewalls registered and reachable for panos_commit_push to succeed.

3. Configuration Reference

This is the complete parameter reference. Parameters split into four groups:

  1. Provider parameters — credentials and connection details (provider dict).
  2. Scope parameters — where in PAN-OS the operation targets (vsys, device_group, template, …).
  3. State and movement parameters — state, location, audit_comment, target, …
  4. Resource parameters — fields specific to the object the module manages.

3.1 Provider parameters (provider dict)

Source: plugins/doc_fragments/fragments.py, PROVIDER fragment.

Key Type Required Default Description
ip_address str yes — IP/hostname of the firewall or Panorama
username str no admin Account name (ignored if api_key set)
password str no — Password (ignored if api_key set)
api_key str no — XML API key (use instead of user/pass)
port int no 443 TLS port
serial_number str no — If ip_address is a Panorama, target a managed firewall by serial

The transitional_provider fragment also exposes the deprecated classic params (ip_address, username, password, api_key, port) directly on the task — these are kept for backwards compat but are scheduled for removal in 4.0.0.

3.2 Scope parameters

Parameter Source fragment Default Description
vsys vsys vsys1 Firewall vsys for objects/interfaces
vsys vsys_shared shared Vsys for shared services (syslog/snmp profiles)
vsys vsys_import (none) Vsys to import into for importable objects
vsys_dg vsys_dg vsys1 (NGFW) / shared (Pano) Combined vsys+device-group
device_group device_group shared Panorama device group
template template_only / full_template_support (none) Panorama template
template_stack full_template_support (none) Panorama template stack
rulebase rulebase pre-rulebase (Pano) / rulebase (NGFW) Pre/Post rulebase

3.3 State parameters

Parameter Source fragment Choices Default
state state present / absent present
state enabled_state present / absent / enabled / disabled present
state network_resource_module_state present / absent / replaced / merged / deleted / gathered present

3.4 Movement and rule parameters (policy modules)

Parameter Source fragment Choices Description
location movement top / bottom / before / after Position of the new rule
existing_rule movement str When location=before/after, the relative rule
target target list Restrict rule to specific firewall serials
negate_target target bool Negate the target list
audit_comment audit_comment str Audit message for the rule (only on change)
uuid uuid str Read-only rule UUID (advanced)
gathered_filter gathered_filter str Filter expression for state: gathered

3.5 Per-module-family resource parameters

The collection has a few major module families. Below is the parameter set for each.

Address-object family

Module Key parameters
panos_address_object name, value, address_type (ip-netmask/ip-range/fqdn/ip-wildcard), description, tag
panos_address_group name, static_value (list), dynamic_value (str expression), description, tag
panos_service_object name, protocol (tcp/udp), source_port, destination_port, description, tag
panos_service_group name, value (list), tag
panos_application_object name, category, subcategory, technology, risk, etc.
panos_application_group name, value (list)
panos_application_filter name, category, subcategory, technology, risk, etc.
panos_tag_object name, color (40+ named choices) or color_value (XML), comments
panos_schedule_object name, disable_override, type (recurring/non-recurring), schedule rules
panos_custom_url_category name, url_value, type, description
panos_dynamic_user_group name, description, filter, tag
panos_edl (External Dynamic List) name, source, repeat, description
panos_region name, address, latitude, longitude

Policy family

Module Key parameters
panos_security_rule rule_name, source_zone, source_ip, source_user, destination_zone, destination_ip, application, service, category, action (allow/deny/drop/reset-client/reset-server/reset-both), log_setting, log_start, log_end, description, rule_type (universal/intrazone/interzone), tag_name, negate_source, negate_destination, disabled, schedule, icmp_unreachable, disable_server_response_inspection, group_profile, antivirus, vulnerability, spyware, url_filtering, file_blocking, data_filtering, wildfire_analysis, group_tag
panos_nat_rule2 name, description, nat_type (ipv4/nat64/nptv6), from_zones, to_zones, to_interface, service, source_addresses, destination_addresses, source_translation_* family (type, address_type, interface, ip_address, translated_addresses, fallback_, static_), destination_translated_address, destination_translated_port, ha_binding (primary/both/0/1), disabled, tags, destination_dynamic_translated_* family, group_tag
panos_pbf_rule name, description, tags, from_type (zone/interface), from_value, source_addresses, source_users, negate_source, destination_addresses, negate_destination, applications, services, schedule, disabled, action (forward/forward-to-vsys/discard/no-pbf), forward_* family, enable_enforce_symmetric_return, symmetric_return_addresses, group_tag
panos_decryption_rule name, description, source/destination zones+IPs, applications, action
panos_match_rule Test traffic against rules (replaces panos_query_rules)

Network family

Module Purpose
panos_interface Ethernet data-port (if_name, mode, ip, enable_dhcp, zone_name, vr_name)
panos_aggregate_interface Aggregate (LACP) interface
panos_l2_subinterface, panos_l3_subinterface Sub-interfaces
panos_loopback_interface, panos_tunnel, panos_vlan_interface Special interface types
panos_zone Security zone (zone, mode tap/virtual-wire/layer2/layer3/external, interface, zone_profile, enable_userid)
panos_vlan, panos_virtual_wire L2 plumbing
panos_virtual_router Legacy VR (name, interface, ad_* distances)
panos_logical_router, panos_logical_router_vrf New advanced-routing engine
panos_logical_router_vrf_static_route, panos_logical_router_vrf_ospf_area Advanced routing details
panos_static_route Static route on a VR (name, destination, nexthop_type ip-address/discard/none/next-vr, nexthop, metric, virtual_router, interface, enable_path_monitor, failure_condition)
panos_redistribution Redistribution profile
panos_bgp, panos_bgp_* (12 BGP modules) BGP configuration
panos_ipsec_tunnel, panos_ipsec_profile, panos_ipsec_ipv4_proxyid IPsec
panos_ike_gateway, panos_ike_crypto_profile IKE
panos_gre_tunnel GRE
panos_management_profile Interface management profile
panos_dhcp, panos_dhcp_relay, panos_dhcp_relay_ipv6_address DHCP

Device / system family

Module Purpose
panos_facts Gather facts (gather_subset of system/session/interfaces/ha/routing/vr/vsys/config)
panos_check Wait for autocommit / device readiness
panos_op Run an arbitrary op command (cmd, cmd_is_xml, ignore_disconnect, vsys)
panos_administrator Add/remove/rotate admin users
panos_admin (deprecated) Subset of panos_administrator
panos_admpwd Set admin password via SSH (used for first-boot)
panos_api_key Generate an API key
panos_cert_gen_ssh Generate certificates via SSH (first-boot)
panos_lic License install
panos_software Install PAN-OS version (version, download, install, restart, sync_to_peer, timeout, named_config, perform_software_check)
panos_dynamic_updates Content / antivirus / app updates
panos_restart Reboot the firewall
panos_loadcfg Load a saved/named config
panos_mgtconfig Management config (DNS, NTP, hostname)
panos_active_in_ha Verify node is HA-active
panos_ha HA configuration
panos_readiness_checks Pre-upgrade readiness (panos-upgrade-assurance)
panos_state_snapshot, panos_snapshot_report Pre/post upgrade snapshots
panos_userid User-ID API
panos_registered_ip, panos_registered_ip_facts Dynamic registered IPs
panos_export Export config / certs / pcaps / tech-support (28 categories)
panos_import Import config / certs / pcaps
panos_config_element Raw XML config element get/set/edit/delete
panos_type_cmd Raw type=keygen/op/config
panos_vm_auth_key VM-Series auth-key

Profile family

Module Purpose
panos_log_forwarding_profile (+ _match_list, _match_list_action) Log forwarding
panos_syslog_profile, panos_syslog_server Syslog server profile
panos_snmp_profile, panos_snmp_v2c_server, panos_snmp_v3_server SNMP server profile
panos_email_profile, panos_email_server Email server profile
panos_http_profile, panos_http_profile_header, panos_http_profile_param, panos_http_server HTTP server profile
panos_pg (security profile group) Profile group (virus/spyware/vulnerability/url/file/data/wildfire)

Panorama family

Module Purpose
panos_device_group Manage device groups (name, parent, devices)
panos_template, panos_template_stack, panos_template_variable Templates
panos_commit_panorama Commit Panorama itself
panos_commit_push Push to managed devices (style, name, admins, include_template, force_template_values, devices, sync)

3.6 Doc fragments inventory (16 total)

From plugins/doc_fragments/fragments.py:

Fragment Adds
provider The provider dict
transitional_provider provider + classic params (deprecated)
state state: present/absent
enabled_state state: present/absent/enabled/disabled
network_resource_module_state state: present/absent/replaced/merged/deleted/gathered
vsys vsys (default vsys1)
vsys_shared vsys (default shared)
vsys_import vsys for imported objects
vsys_dg combined vsys_dg
device_group device_group (default shared)
template_only template (Panorama)
full_template_support template + template_stack
rulebase rulebase: pre-rulebase/rulebase/post-rulebase
deprecated_commit Legacy commit: bool flag (deprecated)
uuid, movement, target, audit_comment, gathered_filter Policy-rule extras

3.7 meta/runtime.yml deprecation routing

Twelve modules redirect to a successor and will be removed in 4.0.0:

Old Redirected to Reason
panos_admin panos_administrator Subset of admin module
panos_commit panos_commit_firewall Replaced by 3 specialised commits
panos_dag panos_address_group Idempotent rewrite
panos_dag_tags panos_registered_ip Modern API
panos_object panos_address_object Idempotent rewrite
panos_object_facts (no redirect) Replaced by state: gathered
panos_nat_rule panos_nat_rule2 Network-resource rewrite
panos_nat_rule_facts (no redirect) Use state: gathered
panos_query_rules panos_match_rule Better implementation
panos_sag panos_address_group Static address group rewrite
panos_security_rule_facts panos_security_rule Use state: gathered
panos_virtual_router_facts panos_virtual_router Use state: gathered
panos_zone_facts panos_zone Use state: gathered

4. Deployment & Scaling

Single control node (default)

The simplest topology: one Linux host (laptop, jump server, CI runner) with the collection installed, talking to one or many firewalls. No persistence, no shared state — every run starts cold. Forks scale the parallelism:

ansible-playbook -i hosts.yml site.yml --forks 25

forks controls how many firewalls are configured in parallel. Each fork is an isolated Python process, so there's no shared SDK state between them.

Tip

For 100+ firewalls, set forks: 50–100 and use serial: <N> in the playbook to batch the rollout. PAN-OS handles concurrent API requests well, but you can saturate Panorama if every firewall is doing a commit_push simultaneously.

Ansible Automation Platform (AAP) / AWX

AAP is the supported enterprise deployment mode. Build an Execution Environment (EE) container image:

# execution-environment.yml
version: 3
images:
  base_image:
    name: quay.io/ansible/awx-ee:latest
dependencies:
  galaxy:
    collections:
      - name: paloaltonetworks.panos
        version: ">=3.0.0,<4.0.0"
  python:
    - pan-os-python>=1.12.4,<2.0
    - panos-upgrade-assurance>=2.0.0,<3.0
    - xmltodict>=0.12.0
    - dpath>=2.1.5

Build with ansible-builder build and push to your registry. AAP job templates point at the EE and execute playbooks on schedule or on demand.

CI/CD scheduler

The collection is fully scriptable from CI. A typical GitLab/GitHub Actions job:

- name: Apply firewall config
  run: |
    pip install ansible-core>=2.16 pan-os-python panos-upgrade-assurance xmltodict dpath
    ansible-galaxy collection install paloaltonetworks.panos
    ansible-playbook -i inventory/prod site.yml \
      --vault-password-file <(echo "$ANSIBLE_VAULT_PW")
  env:
    PANOS_PASSWORD: ${{ secrets.PANOS_PASSWORD }}
    ANSIBLE_VAULT_PW: ${{ secrets.ANSIBLE_VAULT_PW }}

Event-Driven Ansible (EDA)

The collection ships an EDA event source at extensions/eda/plugins/event_source/logs.py (declared in meta/extensions.yml). This lets a rulebook react to PAN-OS log events — for example, auto-block a malicious IP after a threat log:

# rulebook.yml
- name: Auto-block on threat
  hosts: all
  sources:
    - paloaltonetworks.panos.logs:
        host: 0.0.0.0
        port: 5514
  rules:
    - name: Block bad actor
      condition: event.threatid == 'malware-foo'
      action:
        run_playbook:
          name: playbooks/auto_block.yml

Scaling concerns

Concern Mitigation
Slow Panorama commits at scale Use panos_commit_panorama with device_groups: [...] to limit scope; chunk pushes with serial:
API rate limits Keep forks ≤ 50 for a single Panorama; spread across multiple Panoramas if you have HA pairs
Long ops timing out Bump timeout on panos_software, panos_export, panos_op
Fork-per-firewall RAM ~50 MB per Python fork; budget accordingly

5. Operations

5.1 Day-to-day runbook

Task Module(s) Cadence
Verify firewall reachability panos_check Daily smoke test
Pull config backup panos_export (category: configuration) Daily
Snapshot pre-change state panos_state_snapshot Per-change
Push config from Panorama panos_commit_panorama + panos_commit_push Per-change
Validate post-change panos_facts, panos_op (show jobs all) Per-change
Rotate API keys panos_api_key + update vault Quarterly
Pre-stage a new PAN-OS image panos_software (install: false) Per release

5.2 Health checks

- name: Daily health check
  hosts: firewalls
  connection: local
  gather_facts: false

  tasks:
    - name: Confirm firewall is responsive
      paloaltonetworks.panos.panos_check:
        provider: "{{ provider }}"
        timeout: 60

    - name: Gather session and HA facts
      paloaltonetworks.panos.panos_facts:
        provider: "{{ provider }}"
        gather_subset: ['system', 'session', 'ha']
      register: facts

    - name: Alert if HA out of sync
      ansible.builtin.fail:
        msg: "HA out of sync on {{ inventory_hostname }}"
      when:
        - ansible_net_ha_enabled | default(false)
        - ansible_net_ha_localstate not in ['active', 'passive']

5.3 Backup runbook

- name: Daily firewall backups
  hosts: firewalls
  connection: local
  gather_facts: false

  tasks:
    - name: Export config
      paloaltonetworks.panos.panos_export:
        provider: "{{ provider }}"
        category: 'configuration'
        filename: "/var/backups/panos/{{ inventory_hostname }}-{{ ansible_date_time.date }}.xml"
        create_directory: true

Caution

Backup XML files are sensitive — they contain admin password hashes, downstream credentials, and certificate private keys. See §7.4.

5.4 Monitoring integration

panos_facts returns a structured dict that maps cleanly to Prometheus exporters or to a JSON-over-HTTP endpoint:

- name: Publish metrics
  ansible.builtin.uri:
    url: "https://metrics.example.com/api/v1/panos"
    method: POST
    body_format: json
    body: "{{ ansible_facts | combine({'host': inventory_hostname}) | to_json }}"

Note

The collection deliberately does not ship monitoring code — it produces facts; you decide how to ship them. Common patterns: write to Prometheus textfile collector, push to InfluxDB, or POST to an internal metrics API.

5.5 Disaster recovery

Recovery from "firewall config corrupted":

  1. Find the most recent good backup XML.
  2. SCP it to the firewall (or use the restore-via-import workflow).
  3. panos_op with cmd: '<load><config><from>good.xml</from></config></load>' and cmd_is_xml: true.
  4. panos_commit_firewall.

Recovery from "firewall offline / replaced":

  1. Boot the new firewall to factory defaults.
  2. panos_admpwd to set the admin password (via SSH).
  3. panos_lic to install the license.
  4. panos_loadcfg to load the previously exported running config.
  5. panos_commit_firewall to activate.

Tip

For a full unattended bootstrap, chain the modules above with panos_check between each step so the next module waits until the previous has finished.

5.6 Operational pre-flight checklist

  • [x] pan-os-python installed on the control node
  • [x] panos-upgrade-assurance installed (for the upgrade modules)
  • [x] provider vars vaulted, never plaintext in playbook YAML
  • [x] ansible_python_interpreter pinned in inventory
  • [x] Collection version pinned in requirements.yml
  • [ ] Backup runbook scheduled in CI
  • [ ] EDA rulebook deployed for log-driven response (optional)

6. Performance

6.1 Where time goes

A rough breakdown for a typical "add 100 address objects + 50 rules + commit" playbook against a real PA-VM:

Phase Wall time Notes
Connection setup (PanDevice.create_from_device) ~1.5 sec Once per task
Per-object refresh + diff ~80 ms Each object module reads then writes
XML set request ~50 ms Each individual object create
Commit 30 sec – 5 min Depends on candidate size and device model
Commit push (Panorama → device) 1–10 min Per device

6.2 Tuning levers

Knob Effect
forks: <N> (Ansible) Parallelize across hosts. ~50 is a safe ceiling for one Panorama.
serial: <N> (Ansible) Roll out to N hosts at a time — reduces blast radius
gather_subset Limit panos_facts to what you need; config is heavy
sync: false (commit modules) Return immediately with a jobid; poll later
timeout Per-module; bump for long ops
poll_interval / poll_timeout (panos_op) XML API job polling

6.3 Common performance pitfalls

  • gather_subset: ['all'] against Panorama — Panorama only supports system, ha, config subsets. Asking for interfaces is wasted time.
  • Many small commits — batch all your changes in one play, then commit once. Each commit takes 30+ seconds on a real firewall.
  • panos_op in a tight loop — every op is a full HTTP round trip plus a job poll. Consider whether one structured XML show command can replace 20 CLI commands.
  • No sync: false on long pushes — if you're pushing to 50 device groups, run them async and poll for completion at the end.

Note

The 5 MB XML body limit (in plugins/httpapi/panos.py:578) is a hard cap. If you ever hit Data too large for XML API request, the only fix is to split the request — there's no server-side knob.


7. Security

7.1 Threat model

The control node holds credentials that have full administrative control over PAN-OS firewalls and Panorama. A compromised control node = compromised firewall fleet. Every recommendation below treats that as the central threat.

7.2 Credential handling

  • Never check unencrypted credentials into source control. Always use ansible-vault encrypt_string:

    ansible-vault encrypt_string --stdin-name 'vault_panos_pw'
    # paste password, ctrl-D
    
  • Set ANSIBLE_VAULT_PASSWORD_FILE (or --vault-password-file) so you never type the master vault password into shell history:

    export ANSIBLE_VAULT_PASSWORD_FILE=$HOME/.ansible/vault_pw
    chmod 600 $HOME/.ansible/vault_pw
    
  • Use no_log: true on every task that handles a secret. The collection's modules already mark the relevant parameters (api_key, password, admin_password, admin_phash, etc.) with no_log=True in their argument spec, but Ansible may still print parameter values via the debug task — no_log: true on the task is the belt-and-braces step:

    - name: Set admin password
      paloaltonetworks.panos.panos_administrator:
        provider: "{{ provider }}"
        admin_username: 'automation'
        admin_password: "{{ vault_automation_pw }}"
        superuser: true
        state: present
      no_log: true
    

Important

no_log: true on a task suppresses parameter logging, but it does not suppress what your module returns in register:. If you register: result after a sensitive task, then debug: var=result, you may still print secrets. Audit your debugging.

7.3 Secrets handling

  • API keys are bearer tokens equivalent to admin credentials. Treat them like passwords — vault them, rotate them, never commit them.
  • Admin password hashes (admin_phash) are reusable: anyone who has the hash can paste it back in via panos_administrator and inherit that admin's role. Treat hashes like passwords.
  • Downstream service credentials (LDAP, RADIUS, SNMPv3, syslog over TLS, email server passwords) are part of the device configuration, so a configuration export is sensitive too (see 7.4).

7.4 Protect configuration backups

Configuration backups taken with panos_export (category: configuration), with panos_op (<show><config><running></running></config></show>) or with HttpApi.get(xpath="/config") are full XML copies of the device configuration. They can include administrator password hashes, certificates and keys, and downstream service credentials, so handle them as credential material:

  • Restrict and encrypt. Limit ACLs to the backup operator (chmod 600), encrypt at rest (disk-level or per-file GPG), and keep them in a private bucket or share with server-side encryption and a retention policy.
  • Keep API keys on a cadence. Rotate the automation API key on a known schedule (see 7.6) and after a backup has been handled outside the operator host.
  • Plan for exposure. If a backup is ever exposed, follow your incident process: rotate the credentials and re-issue the certificates it contained.

For how PAN-OS protects stored secrets (including the master key), use the official PAN-OS administrator documentation as the reference, not this guide.

7.5 Network security

  • The firewall management plane should never be exposed on the public Internet. Restrict TCP/443 to the control-node CIDR.
  • Use TLS verification end-to-end. The httpapi plugin's ansible_httpapi_validate_certs: false is acceptable in lab; in production, install a real CA-signed cert on the firewall and enable validation.
  • Restrict the firewall's management profile (or whole management vsys) to the control plane.
  • For Panorama, separate the management network from the production data plane.

7.6 Authentication and authorization

  • Use a dedicated ansible admin role with only the permissions Ansible needs. Don't use superuser if read-only would do.
  • Rotate the API key quarterly via panos_api_key (or PAN-OS native debug api-key).
  • Use authentication profiles (LDAP/SAML/RADIUS) for human admins; reserve local admins for automation accounts only.
  • Enable PAN-OS audit logging for all API actions; ship audit logs to a SIEM.

7.7 Code-supply-chain security

  • Pin the collection version in requirements.yml (>=3.0.0,<4.0.0).
  • Pin Python deps in your control-node lock file. Don't blindly upgrade pan-os-python mid-release.
  • pan-os-python is signed and published from PyPI by Palo Alto Networks. Verify with pip download --require-hashes if you mirror.
  • Signed releases of the collection itself live on Galaxy and as GitHub release assets (signed via semantic-release per .releaserc.json).

7.8 Hardening checklist

  • [x] All credentials in ansible-vault encrypt_string-protected vars
  • [x] no_log: true on every task that handles a secret
  • [x] Backup files have restricted ACLs (chmod 600, owned by backup operator only)
  • [x] Backup files are encrypted at rest (disk-level or per-file GPG)
  • [x] API keys are rotated on a known cadence (e.g., quarterly, plus after any backup leaves the operator host)
  • [x] Vault password file has chmod 600
  • [x] Per-firewall least-privilege admin role for automation (not superuser)
  • [x] Firewall management plane locked to the control-node CIDR

8. Developer Internals

8.1 Repository layout

pan-os-ansible/
├── galaxy.yml                  # Collection metadata: namespace, version, deps
├── meta/
│   ├── runtime.yml             # requires_ansible >=2.16; deprecation routing
│   └── extensions.yml          # EDA extension registration
├── plugins/
│   ├── modules/                # 118 task modules — the public surface
│   ├── module_utils/
│   │   └── panos.py            # ConnectionHelper + get_connection() (~1900 lines)
│   ├── httpapi/
│   │   └── panos.py            # HTTP API connection plugin (~640 lines)
│   └── doc_fragments/
│       └── fragments.py        # 16 reusable doc fragments
├── extensions/
│   └── eda/
│       ├── plugins/event_source/logs.py
│       └── rulebooks/decryption.yaml
├── tests/
│   ├── integration/            # Live-firewall idempotency tests
│   │   ├── README.md
│   │   ├── ansible.cfg
│   │   ├── inventory.example
│   │   ├── reset.yml           # Reset firewall to blank config
│   │   ├── run_all_tests.yml
│   │   ├── run_single_test.yml
│   │   ├── setup.yml
│   │   ├── firewall/test_panos_*.yml
│   │   ├── panorama/test_panos_*.yml
│   │   └── group_vars/all.yml
│   ├── unit/
│   │   ├── httpapi/test_panos.py
│   │   ├── plugins/module_utils/test_panos.py
│   │   └── plugins/modules/common/utils.py     # AnsibleExitJson harness
│   └── sanity/
│       └── ignore-2.16.txt … ignore-2.20.txt
├── docs/
│   ├── source/                 # Sphinx config for autodoc
│   ├── templates/module.rst.j2
│   └── contributing.md
├── requirements.txt
├── pyproject.toml
├── Makefile
├── README.md
├── CHANGELOG.md
└── .releaserc.json             # semantic-release config

8.2 Module structure: anatomy of a panos_* module

Every module follows the same five-section layout. Take plugins/modules/panos_address_object.py as the canonical example:

  1. License header — Apache 2.0.
  2. DOCUMENTATION string — module name, short description, author, version_added, requirements, notes, doc fragments referenced via extends_documentation_fragment, and module-specific options:.
  3. EXAMPLES string — copy-pasteable YAML examples.
  4. RETURN string — what the module returns to the caller.
  5. main() function — calls get_connection() to build a helper, then helper.process(module).

Example (panos_address_object.py, abridged):

def main():
    helper = get_connection(
        vsys=True,
        device_group=True,
        with_classic_provider_spec=True,
        with_network_resource_module_state=True,
        with_commit=True,
        with_gathered_filter=True,
        sdk_cls=("objects", "AddressObject"),
        sdk_params=dict(
            name=dict(required=True),
            value=dict(),
            address_type=dict(
                default="ip-netmask",
                choices=["ip-netmask", "ip-range", "fqdn", "ip-wildcard"],
                sdk_param="type",
            ),
            description=dict(),
            tag=dict(type="list", elements="str"),
        ),
    )

    module = AnsibleModule(
        argument_spec=helper.argument_spec,
        required_one_of=helper.required_one_of,
        supports_check_mode=True,
    )

    helper.process(module)

That's the entire module body. The get_connection() call is declarative: it tells the helper which scope and capabilities to set up, and which SDK class (objects.AddressObject) maps to which Ansible parameters.

8.3 ConnectionHelper deep dive

Source: plugins/module_utils/panos.py. The ConnectionHelper class is the heart of the collection. Its lifecycle:

class ConnectionHelper(object):
    def __init__(
        self,
        min_pandevice_version,
        min_panos_version,
        min_panos_upgrade_assurance_version,
        error_on_firewall_shared,
        panorama_error,
        firewall_error,
        template_is_optional,
    ):
        # Sets up argument_spec, required_one_of, and the
        # version-min checks for pan-os-python and PAN-OS itself.
        ...

    def get_pandevice_parent(self, module, timeout=0):
        # 1. Verify pan-os-python (and panos-upgrade-assurance) are
        #    installed at the required minimum version.
        # 2. Build the auth tuple from provider or classic params.
        # 3. PanDevice.create_from_device(*auth) — returns Firewall or
        #    Panorama instance.
        # 4. Verify min_panos_version against device's _version_info.
        # 5. Walk down to the right scope: vsys, device_group, template,
        #    rulebase. Returns the parent SDK object.
        ...

    def process(self, module):
        # The orchestrator. Calls:
        #   - get_pandevice_parent()
        #   - initial_handling(module)            (subclass hook)
        #   - parent_handling(parent, module)     (subclass hook)
        #   - sdk_cls.refresh(parent)
        #   - spec_handling(spec, module)         (subclass hook)
        #   - object_handling(obj, module)        (subclass hook)
        #   - pre_state_handling(obj, result, module)   (subclass hook)
        #   - apply_state(obj, listing, module, ..., result)
        #   - post_state_handling(obj, result, module)  (subclass hook)
        # Then exits the module with the result dict.
        ...

    def apply_state(self, obj, listing, module, ..., result):
        # The diff-and-apply engine. For each state:
        #   - present: create or update
        #   - absent / deleted: delete
        #   - replaced: full overwrite
        #   - merged: additive merge
        #   - gathered: read only, return matching items via
        #     matches_gathered_filter()
        ...

    def commit(self, module, include_template=False, admins=None):
        # The shared commit helper used by older modules' with_commit=True.
        # New modules use the dedicated commit modules instead.
        ...

8.4 Subclassing ConnectionHelper for module-specific behaviour

Many modules need extra logic — pre-flight, special spec handling, post-state side-effects. They subclass ConnectionHelper and override hooks. Example: plugins/modules/panos_security_rule.py:

class Helper(ConnectionHelper):
    def initial_handling(self, module):
        # Migrate the deprecated "devicegroup" param to "device_group"
        if module.params["devicegroup"] is not None:
            module.deprecate(...)
            module.params["device_group"] = module.params["devicegroup"]

    def spec_handling(self, spec, module):
        # PAN-OS 10+ removed hip_profiles; supply a default for older
        # versions so we don't break existing playbooks.
        if module.params["state"] not in ("present", "replaced"):
            return
        if self.device._version_info < (10, 0, 0) and spec["hip_profiles"] is None:
            spec["hip_profiles"] = ["any"]

def main():
    helper = get_connection(
        helper_cls=Helper,         # custom subclass
        ...
    )

The hooks (in execution order):

  1. initial_handling(module) — before any device interaction. Validate or migrate parameters.
  2. parent_handling(parent, module) — after the parent SDK object is built, before the resource is fetched.
  3. spec_handling(spec, module) — mutate the parameter dict before it is serialized to XML.
  4. object_handling(obj, module) — mutate the SDK object before apply.
  5. pre_state_handling(obj, result, module) — final hook before apply_state. Used for hashing passwords (e.g. panos_administrator).
  6. apply_state(...) — the framework's diff-and-apply engine. Not usually overridden.
  7. post_state_handling(obj, result, module) — after apply. Used for post-write side effects (e.g., refreshing API key after admin's own password change in panos_administrator).

8.5 Idempotency / diff logic

The framework computes idempotency by:

  1. Fetching the live SDK object via parent.refresh(obj).
  2. Comparing each field in sdk_params between the desired spec and the live object.
  3. If they match → changed=False, return.
  4. If they differ → mutate the live object, call obj.apply() (or obj.create() / obj.delete() as appropriate), set changed=True.

The state: replaced mode is a full overwrite — fields not in the spec are set to defaults / nulls. state: merged is additive.

state: gathered uses matches_gathered_filter from plugins/module_utils/panos.py:1220 to evaluate the user's filter expression (e.g., name == 'Web-Server-A') against each candidate object, returning matching specs.

8.6 The HTTP API plugin (plugins/httpapi/panos.py)

The httpapi plugin is a separate code path that lets users configure the collection via Ansible's httpapi connection model rather than the provider dict. It's a 640-line module that:

  • Caches the API key per connection (via keygen if not provided)
  • Implements XML API verbs as Python methods: show, get, set, edit, delete, rename, move, override, commit, commit_all, op, version, poll_for_job
  • Exposes is_panorama() to differentiate Firewall from Panorama
  • Maps PAN-OS API error codes (_PANOS_API_ERROR_CODES dict) to a PanOSAPIError exception with code + name
  • Enforces a 5 MB body limit (line 578) to match the XML API server-side cap
  • Handles per-request auth via update_auth() — uses the X-PAN-KEY header on PAN-OS 9.0+

8.7 Test harness

Unit tests

Under tests/unit/. Use pytest plus a custom harness in tests/unit/plugins/modules/common/utils.py:

class ModuleTestCase:
    @pytest.fixture(autouse=True)
    def module_mock(self, mocker):
        return mocker.patch.multiple(
            basic.AnsibleModule, exit_json=exit_json, fail_json=fail_json
        )

    @pytest.fixture
    def connection_mock(self, mocker):
        connection_class_mock = mocker.patch(
            "ansible_collections.paloaltonetworks.panos.plugins.module_utils.panos.Connection"
        )
        connection_class_mock.return_value.api_key.return_value = "foo"
        connection_class_mock.return_value.get_device_info.return_value = {
            "hostname": "PA-VM",
            "model": "PA-VM",
            "serial": "serial",
            "version": "10.0.0",
            "uptime": "uptime",
            "multivsys": "multivsys",
        }
        return connection_class_mock.return_value

The harness mocks out AnsibleModule.exit_json / fail_json so the test can assert on the args without actually exiting the test process. connection_mock provides a mock httpapi connection.

Integration tests

Under tests/integration/. These are live-firewall tests — they require a real (or VM) PAN-OS device. The tests/integration/README.md describes the expected initial config:

  • ethernet1/1 in outside zone
  • ethernet1/2 in inside zone
  • DHCP management configuration
  • a CA certificate with key named local-ca marked as forward trust

And for Panorama:

  • Test-DG device group
  • Test-Template template

Run a single test:

ansible-playbook -i inventory.ini run_single_test.yml -e test=test_panos_address_object

Run all tests:

ansible-playbook -i inventory.ini run_all_tests.yml

Each test pattern:

  1. import_tasks: 'reset.yml' — restore from blank.xml.
  2. Create the resource — assert changed=True.
  3. Re-create with same params — assert changed=False (idempotency).
  4. Modify the resource — assert changed=True.
  5. Delete the resource — assert changed=True.
  6. panos_commit_firewall — assert success.

Important

Idempotency is the contract every module must honour. A new module is not merge-ready until it passes the create-create-modify-delete-commit cycle in tests/integration/.

8.8 Adding a new module

The contributing workflow (per docs/contributing.md) is roughly:

  1. Identify the SDK class in pan-os-python that maps to your PAN-OS resource (e.g., panos.objects.AddressObject, panos.network.VirtualRouter).
  2. Copy a similar module (an existing simple one — panos_tag_object.py is a good starter) into plugins/modules/panos_<resource>.py.
  3. Write the DOCUMENTATION, EXAMPLES, RETURN strings.
  4. In main(), call get_connection() with the right sdk_cls, sdk_params, vsys / device_group / template flags, and any with_* capability flags (with_commit, with_movement, with_uuid, with_target, etc.).
  5. If you need custom behaviour, subclass ConnectionHelper and override the appropriate hook (see §8.4).
  6. Add an integration test under tests/integration/firewall/ (and panorama/ if applicable). Mirror the create-modify-delete-idempotency pattern.
  7. Run ansible-test sanity from the collection root and fix any new warnings, or add to the relevant tests/sanity/ignore-*.txt if there's a justified pinning.
  8. Open a PR using the conventionalcommits preset (feat: / fix: / chore: …) — semantic-release will use this to compute the next version number.

Tip

Before writing a custom subclass, check the 16 doc fragments and the existing with_* flags. 90 % of new modules don't need a subclass — they only need the right declarative flags in get_connection().

8.9 Sanity tests and Ansible-version compatibility

The collection is sanity-tested against Ansible Core 2.16 → 2.20. Each target version has its own ignore-list at tests/sanity/ignore-2.<N>.txt that lists known-acceptable warnings (mostly old-Python-syntax warnings that don't affect runtime). When you add a module, run:

ansible-test sanity --python 3.10 --docker

Add new ignores only when you understand the warning and have agreed it's acceptable.

8.10 Release pipeline

.releaserc.json configures semantic-release to:

  1. Analyse commits since the last tag (conventionalcommits preset).
  2. Compute the next semver version (patch / minor / major).
  3. Update version in galaxy.yml, pyproject.toml, docs/source/index.rst.
  4. Update CHANGELOG.md.
  5. Tag and push.
  6. Build the Galaxy tarball; publish to Ansible Galaxy and as a GitHub release asset.

The collection is shipped to: https://galaxy.ansible.com/ui/repo/published/paloaltonetworks/panos.

8.11 Potential improvements

These are open opportunities visible in the source and issue tracker:

  • IPv6 in the advanced-routing modules. panos_logical_router_vrf_static_route doesn't yet support IPv6 next-hop (#660).
  • Skip-version upgrade logic in panos_software. The base-image detection (#662) and post-install hang (#658) could use better diagnostics.
  • Pretty-print XML in panos_export. Issue #636.
  • Multi-line panos_op commands. Issue #637.
  • Action groups in the collection. Issue #635 — group related modules for easier collections: use.
  • Inbound decryption for PAN-OS 10.2+. Issue #401.
  • Selective commit-by-admin in panos_commit_panorama. Issue #628.

9. Integration Points

9.1 Galaxy / collection ecosystem

  • Galaxy URL: https://galaxy.ansible.com/ui/repo/published/paloaltonetworks/panos
  • GitHub: https://github.com/PaloAltoNetworks/pan-os-ansible
  • Documentation site (pan.dev): https://pan.dev/ansible/docs/panos
  • Issue tracker: https://github.com/PaloAltoNetworks/pan-os-ansible/issues
  • Galaxy tags: networking, paloaltonetworks, panw, security, panos (per galaxy.yml).

9.2 SDK and adjacent libraries

  • pan-os-python (panos) — the primary SDK the collection wraps. Same vendor; the collection pins >=1.12.4,<2.0.
  • panos-upgrade-assurance — companion library for pre/post upgrade health checks. Used by panos_active_in_ha, panos_readiness_checks, panos_snapshot_report, panos_state_snapshot. Pinned >=2.0.0,<3.0.
  • pan-python — older transitional SDK. Some legacy modules (e.g., panos_admin, panos_admpwd) still import it.
  • xmltodict — used by panos_op to convert XML responses to JSON.
  • dpath — used by the gathered_filter evaluator.
  • aiohttp — used by the EDA event source for async log streaming.

9.3 Ansible Automation Platform (AAP)

Build an Execution Environment with the collection + Python deps; create job templates that point at it; use AAP's credentials store to inject PANOS_PASSWORD / vault password.

9.4 Event-Driven Ansible (EDA)

The collection registers an EDA extension via meta/extensions.yml:

extensions:
  - args:
      ext_dir: eda/plugins/event_filter
  - args:
      ext_dir: eda/plugins/event_source

The event source at extensions/eda/plugins/event_source/logs.py listens for PAN-OS log events (typically forwarded as syslog) and emits structured events to EDA rulebooks. Use case: auto-respond to a threat log by adding the source IP to a registered-IP tag, then letting a Dynamic Address Group block it.

9.5 SIEM / observability

  • Forward the firewall's logs to Splunk, Sentinel, ELK, or any syslog collector via panos_syslog_profile + panos_syslog_server.
  • Forward to an HTTP endpoint via panos_http_profile + panos_http_server (for, e.g., Datadog or generic webhook receivers).
  • Forward to email via panos_email_profile + panos_email_server.
  • Forward to SNMPv2c/v3 trap receivers via panos_snmp_profile + panos_snmp_v2c_server / panos_snmp_v3_server.

The Ansible side configures the firewall's log-forwarding plumbing; the SIEM side is whatever you have.

9.6 Decryption broker / TLS inspection ecosystem

The panos_decryption_rule module manages decryption policy. Pair with the EDA rulebook at extensions/eda/rulebooks/decryption.yaml for event-driven decryption tuning.

9.7 Panorama → managed devices

Panorama is itself an integration point. The collection writes to Panorama via device_group / template / template_stack parameters, then pushes to managed devices via panos_commit_push. Push styles supported (per panos_commit_push.style):

  • device group
  • template
  • template stack
  • log collector group
  • wildfire appliance
  • wildfire cluster

9.8 First-boot / day-zero pipelines

For VM-Series and PA-7000 first-boot bootstrapping, chain:

  1. panos_admpwd (sets initial admin password via SSH; panos_cert_gen_ssh generates a cert via SSH).
  2. panos_lic (license install).
  3. panos_dynamic_updates (initial content install).
  4. panos_loadcfg (load a baseline config from a saved file).
  5. panos_commit_firewall.
  6. panos_check (wait for ready).

9.9 Test data / sandboxing

Use tests/integration/setup.yml as a model for spinning up a test firewall. The pattern: wait for the PAN-OS device to be ready (panos_check), save a copy of the current config as blank.xml on the device, then run your tests with the ability to reset back to blank between test cases.

9.10 Compliance / hardening references

The collection pairs cleanly with Palo Alto Networks' IronSkillet configuration framework:

  • IronSkillet provides the security-baseline configuration objects.
  • Translate IronSkillet's XML into Ansible tasks using panos_address_object, panos_security_rule, panos_zone, etc.

Or use panos_config_element to push raw XML chunks if you need 1:1 parity with IronSkillet snippets while you migrate.