iron-skillet User Guide

Generated April 2026 from PaloAltoNetworks/iron-skillet at 9833373

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.

iron-skillet is Palo Alto Networks' day-1 configuration baseline for the PAN-OS Next-Generation Firewall (NGFW) and Panorama. It ships a curated set of Jinja2-templated XML snippets plus pre-rendered full-config XML files that bootstrap a fresh device with hardened defaults: SSH-only management, login banners, password complexity, dynamic-update schedules, zone-protection profiles, antivirus / anti-spyware / vulnerability / file-blocking / URL filtering / WildFire profiles, decryption profiles, log-forwarding to syslog and email, scheduled reports, and an opinionated default rulebase.

This guide is for network engineers, security engineers, and consultants who need to bring a brand-new PAN-OS device to a known, auditable security posture in minutes rather than days.


1. Quick Start

The fastest path to a rendered iron-skillet configuration that you can import into a fresh firewall is clone the repo and copy a sample loadable config straight to the device. No template rendering, no Python, no toolchain — the repository ships pre-rendered XML files for every common deployment scenario.

git clone https://github.com/PaloAltoNetworks/iron-skillet.git
cd iron-skillet
ls loadable_configs/
sample-cloud-AWS  sample-cloud-Azure  sample-cloud-GCP
sample-mgmt-dhcp  sample-mgmt-static  sample-set-commands

Pick the scenario that matches your device, then push the matching XML or set-command file to the firewall:

Scenario NGFW file Panorama file
Cloud (AWS) loadable_configs/sample-cloud-AWS/panos/iron_skillet_panos_full.xml loadable_configs/sample-cloud-AWS/panorama/iron_skillet_panorama_full.xml
Cloud (Azure) loadable_configs/sample-cloud-Azure/panos/iron_skillet_panos_full.xml loadable_configs/sample-cloud-Azure/panorama/iron_skillet_panorama_full.xml
Cloud (GCP) loadable_configs/sample-cloud-GCP/panos/iron_skillet_panos_full.xml loadable_configs/sample-cloud-GCP/panorama/iron_skillet_panorama_full.xml
On-prem, DHCP mgmt loadable_configs/sample-mgmt-dhcp/panos/iron_skillet_panos_full.xml loadable_configs/sample-mgmt-dhcp/panorama/iron_skillet_panorama_full.xml
On-prem, static mgmt loadable_configs/sample-mgmt-static/panos/iron_skillet_panos_full.xml loadable_configs/sample-mgmt-static/panorama/iron_skillet_panorama_full.xml
Set-command CLI loadable_configs/sample-set-commands/panos/iron_skillet_panos_full.conf loadable_configs/sample-set-commands/panorama/iron_skillet_panorama_full.conf

Load the XML through the firewall's web UI under Device → Setup → Operations → Import named configuration snapshot, then Load named configuration snapshot, then Commit. For the set-command path, paste the .conf file into the CLI in configure mode.

Tip

If you want a baseline with zero customization — the same defaults the repo authors validated — just load the cloud or DHCP sample that matches your environment and commit. You can iterate on hostnames, NTP servers, and admin credentials after the device is online.

Minimal example

A typical first commit on a fresh NGFW with a static management address:

# 1. Clone
git clone https://github.com/PaloAltoNetworks/iron-skillet.git

# 2. Open the static-mgmt full XML, edit hostname / IP / DNS to taste
$EDITOR iron-skillet/loadable_configs/sample-mgmt-static/panos/iron_skillet_panos_full.xml

# 3. Import into the firewall (UI: Device > Setup > Operations)
#    Then Load named configuration snapshot, then Commit

What you'll need

  • A fresh PAN-OS NGFW or Panorama appliance (physical, virtual, or cloud).
  • Web-UI or CLI admin access to that device.
  • For template rendering (optional): Python 3 with the sli tool, or Panhandler / pan-cnc, or the legacy scripts in tools/archive/.

What to try next

  • Section 2 for the full installation paths (clone vs. Skillet ecosystem vs. archive-script render).
  • Section 4 to understand snippets vs. full-configs and how the variable system works.
  • Section 5 for twelve common recipes — cloud deploys, layering onto existing configs, snippets-only loads, Panorama vs. NGFW.

2. Installation

System requirements

Requirement Detail
Target device PAN-OS NGFW or Panorama, version 10.1 (current panos_v10_1 branch, commit 9833373)
Other PAN-OS versions Older versions live on dedicated branches — see the version-branch note below
Disk A working clone of the repo is ~50 MB
Optional: SLI tool Python 3.6+, pip install sli
Optional: Panhandler Docker (Panhandler is distributed as a container image)
Optional: archive scripts Python 3 with Jinja2>=2.10.1, MarkupSafe==1.0, oyaml==0.7, passlib==1.7.1, PyYAML==5.4, XlsxWriter==1.1.2, requests==2.22.0, pexpect==4.7.0, pan-python==0.14.0 (see tools/archive/requirements.txt)

Important

The default sample configurations contain placeholder credentials — adminuser / adminuser for the firewall admin, 192.0.2.x for IPs, 8.8.8.8 for DNS. Treat any rendered config as secret material: it contains the admin password hash (phash). Never check rendered configs into a public repo, and rotate the admin password after first commit (see Section 5).

Method 1 — Clone and load pre-rendered XML (no toolchain)

This is the lowest-friction path. The repo ships pre-rendered, ready-to-load XML for the six most common scenarios.

git clone https://github.com/PaloAltoNetworks/iron-skillet.git
cd iron-skillet
git checkout panos_v10_1     # or another version branch
ls loadable_configs/sample-cloud-AWS/panos/iron_skillet_panos_full.xml
loadable_configs/sample-cloud-AWS/panos/iron_skillet_panos_full.xml

You now have a complete, valid PAN-OS 10.1 configuration file ready to import into a firewall.

Method 2 — Render a custom full XML with the SLI tool

sli is the maintained command-line skillet runner. It reads the YAML skillet definitions in templates/panos/snippets/.meta-cnc.yaml (and the corresponding Panorama file), substitutes your variable values, and emits a customized XML or set-command output.

mkdir is-work && cd is-work
python3 -m venv ./venv
source ./venv/bin/activate
pip install sli
sli --help

Render the full PAN-OS skillet using the in-repo defaults:

sli template -sd ../iron-skillet -ad \
  -n skillet_full_panos_v10_1 \
  ../my-output-dir
[INFO] Rendered XML files written to ../my-output-dir/

The flags:

  • -sd — skillet directory (the cloned iron-skillet repo).
  • -ad — accept defaults for every variable.
  • -n — skillet name. Confirmed names in this commit: skillet_panos_v10_1 (snippets), ironskillet_panos_10_1 (full playlist), skillet_panorama_v10_1 (Panorama snippets), and ironskillet_panorama_shared_10_1 (Panorama full).

Method 3 — Render with Panhandler (web UI)

Panhandler is a containerized GUI front-end for Skillets. It walks the user through each variable with help text, then renders and (optionally) pushes the config to a device.

docker run -d --name panhandler -p 8080:8080 \
  paloaltonetworks/panhandler:latest

Open http://localhost:8080, import this repo as a skillet collection, pick a playlist (e.g. PAN-OS NGFW IronSkillet 10.1), fill in the variable form, and click Render.

Method 4 — Generate a set-command spreadsheet

If you prefer the CLI set-command path and want a per-variable spreadsheet for distribution to operators, use the SLI spreadsheet generator against the set-command skillet.

sli spreadsheet -n skillet_set_command_panos_v10_1 -o ./out/
out/skillet_set_command_panos_v10_1.xlsx

The output is an Excel file with every variable, its default value, and the rendered set commands; operators edit the variable column and the embedded formulas regenerate the commands.

Method 5 — Legacy archive scripts

The tools/archive/ directory contains the original Python helper scripts the repo's authors used internally before SLI matured. They still work, but the README marks the SLI / Panhandler path as the recommended one.

cd tools/archive
pip install -r requirements.txt
python build_full_templates.py
python create_loadable_configs.py

Warning

The archive scripts pin MarkupSafe==1.0 and PyYAML==5.4, both of which are known to fail to install on modern Python 3.11+ environments. Use Python 3.7–3.9 for archive scripts, or prefer SLI / Panhandler.

Version branches

Each supported PAN-OS major.minor lives on its own branch (e.g. panos_v10_1). The branch you check out determines which template version you render. The repo's .gitmodules and tools/update_submodules.sh keep cross-branch references consistent through the submodules/ directory.

Note

Always check out the branch that matches the target device's PAN-OS version. A 10.1 skillet will not load cleanly on an 8.0 device, and vice versa: PAN-OS XML schema changes with each release. Issue #178 tracks the request for 11.1 / 11.2 / 12.1 branches.

Post-install verification

Confirm the cloned repo and (optional) tooling are wired up:

git -C iron-skillet log -1 --oneline
sli --version
9833373 (HEAD, panos_v10_1) ...
sli, version <x.y.z>

If sli --version reports a version, the toolchain is ready.


3. Configuration

User-facing configuration is the variable substitution layer. The templates are sealed (you don't typically edit XML by hand); you tune the variables, the renderer fills them into the XML, and the resulting file is loaded onto the device.

Where variables live

Three locations, in order of precedence (highest first):

  1. Command-line / GUI override — values typed into Panhandler or passed to SLI override everything below.
  2. Playlist file — playlists/panos/*.skillet.yaml and playlists/panorama/*.skillet.yaml can declare variables that override the defaults from the snippets.
  3. Snippet collection defaults — templates/panos/snippets/.meta-cnc.yaml and templates/panorama/snippets/.meta-cnc.yaml define the master variable list with safe defaults.

Common NGFW variables

The snippets-collection variable inventory for PAN-OS 10.1 (full table in the Admin Guide §3):

Variable Default Purpose
config_mgmt_intf no Render management-interface settings (set yes for greenfield devices)
config_admin_user no Create the admin user from this skillet
config_dns no Apply DNS settings from this skillet
MGMT_TYPE dhcp-client Management IP mode: dhcp-client or static
MGMT_IP 192.0.2.6 Management IP (when MGMT_TYPE == static)
MGMT_MASK 255.255.255.0 Management netmask
MGMT_DG 192.0.2.7 Management default gateway
DNS_1 8.8.8.8 Primary DNS
DNS_2 8.8.4.4 Secondary DNS
FW_NAME panos-01 Firewall hostname
NTP_1 0.pool.ntp.org Primary NTP
NTP_2 1.pool.ntp.org Secondary NTP
API_KEY_LIFETIME 525600 API-key lifetime in minutes (one year)
ADMINISTRATOR_USERNAME adminuser Admin username
ADMINISTRATOR_PASSWORD adminuser Admin password (hashed via md5_hash Jinja filter into phash)
SYSLOG_SERVER 192.0.2.2 Syslog destination
EMAIL_PROFILE_GATEWAY 192.0.2.1 SMTP gateway for email alerts
EMAIL_PROFILE_FROM [email removed] From-address for email alerts
EMAIL_PROFILE_TO [email removed] To-address for email alerts
SINKHOLE_IPV4 sinkhole.paloaltonetworks.com DNS sinkhole FQDN (anti-spyware)
SINKHOLE_IPV6 2600:5200::1 DNS sinkhole IPv6
INCLUDE_PAN_EDL no Include the predefined Palo Alto External Dynamic Lists (requires Threat license)

Common Panorama variables

Variable Default Purpose
config_admin_panorama no Create the Panorama admin user
config_mgmt_intf_panorama no Apply Panorama management-interface IP settings
PANORAMA_TYPE static static or cloud (DHCP)
PANORAMA_IP 192.168.55.7 Panorama management IP
PANORAMA_MASK 255.255.255.0 Panorama netmask
PANORAMA_DG 192.168.55.2 Panorama default gateway
PANORAMA_NAME panorama01 Panorama hostname
STACK sample_stack Template-stack name
DEVICE_GROUP sample_devicegroup Device-group name
CONFIG_EXPORT_IP 192.0.2.3 IP for scheduled config exports

Toggle hints

Many variables are gated by a parent toggle. For example, MGMT_IP only appears in the rendered output when config_mgmt_intf == 'yes' AND MGMT_TYPE == 'static'. SLI and Panhandler hide gated fields in their forms; if you render directly with Jinja, set both the toggle and the target variable.

Note

Variables shown to the operator follow toggle_hint rules. The dropdown type renders as a select-box with a fixed dd_list; text is a free-form input; password masks the value. The actual XML is always written either way — toggle_hint only controls which fields the operator sees.

Configuring against an existing device

The defaults config_mgmt_intf=no, config_admin_user=no, and config_dns=no exist precisely so you can apply IronSkillet without clobbering a device's existing IP, admin, or DNS settings. Set those toggles to yes only on a truly greenfield device.

# inputs.yaml — edit this and pass it to your renderer
config_mgmt_intf: 'no'
config_admin_user: 'no'
config_dns: 'no'
FW_NAME: corp-fw-01
NTP_1: ntp1.example.com
NTP_2: ntp2.example.com
SYSLOG_SERVER: 10.50.1.10

Tip

For a brownfield rollout, the safest first commit is a profiles + rulebase only apply: leave the three config_* toggles at no and use the panos_security_profiles playlist instead of the full one. That installs hardened security profiles without touching admin, management IP, or DNS.

For the complete variable reference, every default, and the playlist override files, see the Admin Guide §3.


4. Core Usage

This section explains how the templates are organized, how the variable system resolves at render time, and which directory you should target for which device.

4.1 Repository layout

iron-skillet/
├── templates/
│   ├── panos/
│   │   ├── snippets/         <-- Jinja-templated XML snippets (HIGH triage)
│   │   ├── full/             <-- Pre-built FULL XML for PAN-OS NGFW
│   │   ├── set_commands/     <-- Pre-built FULL set-command CLI for NGFW
│   │   └── baseline/         <-- baseline.xml — empty starting config
│   └── panorama/
│       ├── snippets/                       <-- Panorama shared-config snippets
│       ├── snippets_dgtemplate_shared/     <-- Shared DG/Template variant
│       ├── snippets_dgstack_notshared/     <-- Not-shared DG-stack variant
│       ├── snippets_notshared/             <-- Not-shared variant
│       ├── full/             <-- Pre-built FULL XML for Panorama
│       ├── set_commands/     <-- Pre-built FULL set commands for Panorama
│       └── baseline/         <-- baseline.xml for Panorama
├── playlists/
│   ├── panos/                <-- 4 NGFW playlists (alert_only, hardening, full, security_profiles)
│   └── panorama/             <-- 6 Panorama playlists
├── loadable_configs/         <-- READY-TO-LOAD XML and set-command files
│   ├── sample-cloud-AWS/
│   ├── sample-cloud-Azure/
│   ├── sample-cloud-GCP/
│   ├── sample-mgmt-dhcp/
│   ├── sample-mgmt-static/
│   └── sample-set-commands/
├── validations/              <-- Skillet-Validation reports (PAN-OS posture checks)
├── tools/                    <-- build_all.sh, update_submodules.sh, archive/
├── docs/                     <-- Sphinx docs source
└── submodules/               <-- Cross-version submodule mounts

4.2 Snippets vs. full configs vs. playlists

Three layers, each higher-level than the last:

  • Snippet (lowest level). A single XML element with a fixed xpath and an element body that may contain Jinja variables. Example: panos_ngfw_device_system_10_1.ironskillet_device_system_timezone writes <timezone>UTC</timezone> to /config/devices/entry[@name='localhost.localdomain']/deviceconfig/system. Snippets are defined in templates/panos/snippets/.meta-cnc.yaml and templates/panorama/snippets/.meta-cnc.yaml.
  • Full config (middle level). The pre-rolled-up XML in templates/panos/full/iron_skillet_panos_full.xml and templates/panorama/full/iron_skillet_panorama_full.xml — every snippet applied in dependency order, ready to render with a single variable substitution pass.
  • Playlist (top level). A .skillet.yaml in playlists/ that selects a curated subset of snippets for a specific use case (panos_alert_only.skillet.yaml, panos_device_hardening.skillet.yaml, panos_full.skillet.yaml, panos_security_profiles.skillet.yaml). Panorama playlists: panorama_shared_full, panorama_shared_dgtemplate, panorama_notshared_full, panorama_notshared_dgstack, panorama_shared_security_profiles, panorama_device_hardening.

Note

The README files in templates/panorama/snippets_notshared/, templates/panorama/snippets_dgstack_notshared/, and templates/panorama/snippets_dgtemplate_shared/ flag those directories as the legacy skillet model with separate .meta-cnc.yaml

  • external XML files. The newer model embeds element bodies inside the YAML — that's what the templates/panos/snippets/.meta-cnc.yaml and templates/panorama/snippets/.meta-cnc.yaml files use.

4.3 Dependency-ordered loading

The snippet name embeds an order: every PAN-OS snippet is prefixed with the parent skillet name (panos_ngfw_device_system_10_1, panos_ngfw_device_setting_10_1, ...). Inside a playlist these names appear in the exact order they must be applied — system before setting, password-complexity before user creation, tags before profiles that reference tags, profiles before profile groups, profile groups before rules, rules before zone protection.

# excerpt of playlists/panos/panos_full.skillet.yaml
snippets:
  - name: panos_ngfw_device_system_mgmt_ip_10_1
  - name: panos_ngfw_device_system_dns_10_1
  - name: panos_ngfw_device_system_10_1
  - name: panos_ngfw_device_setting_10_1
  - name: panos_ngfw_mgt_config_users_10_1     # admin user
  - name: panos_ngfw_password_complexity_10_1
  - name: panos_ngfw_shared_log_settings_10_1  # shared log forwarding
  - name: panos_ngfw_tag_10_1                  # tag objects
  - name: panos_ngfw_log_settings_profiles_10_1
  - name: panos_ngfw_profile_custom_urlFiltering_10_1
  - name: panos_ngfw_profile_decryption_10_1
  - name: panos_ngfw_profile_antivirus_10_1
  - name: panos_ngfw_profile_spyware_10_1
  - name: panos_ngfw_profile_vulnerability_10_1
  - name: panos_ngfw_profile_file-blocking_10_1
  - name: panos_ngfw_profile_urlFiltering_10_1
  - name: panos_ngfw_profile_wildfire_analysis_10_1
  - name: panos_ngfw_profile_group_10_1
  - name: panos_ngfw_rulebase_default_security_rules_10_1
  - name: panos_ngfw_rulebase_security_10_1
  - name: panos_ngfw_rulebase_decryption_10_1
  - name: panos_ngfw_zone_protection_10_1
  - name: panos_ngfw_reports_simple_10_1
  - name: panos_ngfw_report_group_simple_10_1
  - name: panos_ngfw_email_scheduler_10_1

If you re-order these arbitrarily, the device may reject the commit because a rule references a profile group that doesn't yet exist.

4.4 The variable system

Variables are typed. The .meta-cnc.yaml declarations include type_hint (text, password, dropdown, ip_address), an optional dd_list for dropdowns, and an optional toggle_hint that hides the field unless a parent variable equals a particular value. The Jinja filter md5_hash is used exactly once: turning ADMINISTRATOR_PASSWORD into the <phash> element under /config/mgt-config/users.

# inside panos_ngfw_mgt_config_users_10_1
xpath: /config/mgt-config/users
element: |-
    <entry name="{{ ADMINISTRATOR_USERNAME }}">
      <phash>{{ ADMINISTRATOR_PASSWORD | md5_hash }}</phash>
      <permissions>
        <role-based>
          <superuser>yes</superuser>
        </role-based>
      </permissions>
    </entry>
when: config_admin_user == 'yes'

The when: field is a Jinja-evaluated boolean: the snippet renders only if the expression is true at render time.

4.5 Choosing the right version branch

The current snapshot of this guide reflects the panos_v10_1 branch (the PAN-OS 10.1 templates) — names like skillet_panos_v10_1 and version- suffixed snippet names confirm this. Older PAN-OS releases use sibling branches and the submodules/ directory keeps cross-version pointers in sync via tools/update_submodules.sh.

Important

Match the branch to the device's PAN-OS version. The XML schema and feature inventory differ across major.minor releases; loading a 10.1 config onto an 8.x device — or vice versa — will throw "unknown element" errors at commit time.


5. Common Tasks

Real-world recipes against the panos_v10_1 branch. Pick the recipe that matches your situation, follow the steps, verify, and roll back with the "if it fails" notes if anything goes sideways.

5.1 Apply IronSkillet to a fresh NGFW (DHCP management)

Goal: Bring a brand-new firewall online with hardened defaults.

Prereqs: Console access, DHCP server reachable from the management network, web-UI admin password set.

Steps:

  1. Clone the repo: git clone https://github.com/PaloAltoNetworks/iron-skillet.git.
  2. In the firewall web UI, go to Device → Setup → Operations.
  3. Click Import named configuration snapshot and upload loadable_configs/sample-mgmt-dhcp/panos/iron_skillet_panos_full.xml.
  4. Click Load named configuration snapshot and pick the file you just imported.
  5. Click Commit.

Verify:

  • Login banner reads "You have accessed a protected system. Log off immediately if you are not an authorized user."
  • Device → Setup → Services shows DNS 8.8.8.8 / 8.8.4.4 and NTP 0.pool.ntp.org / 1.pool.ntp.org.
  • Objects → Security Profiles → Antivirus has an ironskillet-prefixed profile.

If it fails: Revert by loading the original templates/panos/baseline/baseline.xml through Operations → Load named configuration snapshot, or use Operations → Revert to running configuration if you have not yet committed.

5.2 Customize hostname, NTP, DNS before loading

Goal: Apply IronSkillet but with your organization's NTP and DNS.

Steps:

  1. Render with SLI and override only the variables you care about:
    sli template -sd ./iron-skillet -ad \
      -n skillet_full_panos_v10_1 \
      ./out/
    
    Or, easier: edit the pre-rolled loadable_configs/sample-mgmt-static/panos/iron_skillet_panos_full.xml directly:
    sed -i \
      -e 's|<hostname>panos-01</hostname>|<hostname>corp-fw-01</hostname>|' \
      -e 's|0\.pool\.ntp\.org|ntp1.example.com|' \
      -e 's|1\.pool\.ntp\.org|ntp2.example.com|' \
      -e 's|>8\.8\.8\.8<|>10.0.1.10<|' \
      -e 's|>8\.8\.4\.4<|>10.0.1.11<|' \
      loadable_configs/sample-mgmt-static/panos/iron_skillet_panos_full.xml
    

Verify: Run grep -E '<(hostname|primary|secondary)>' iron_skillet_panos_full.xml and confirm your values appear.

If it fails: git checkout to restore the upstream sample.

5.3 Layer IronSkillet onto an existing partial config

Goal: A device already has IP / admin / DNS configured. You want profiles, log forwarding, rulebase, and zone-protection — nothing else.

Steps:

  1. Render a partial skillet using the panos_alert_only or panos_security_profiles playlist. The first only changes the action on every threat profile to alert; the second installs the full profile suite without touching system settings.
    sli template -sd ./iron-skillet -ad \
      -n ironskillet_panos_security_profiles_10_1 \
      ./out/
    
  2. In the firewall UI, Device → Setup → Operations → Import named configuration snapshot, then Load configuration version (Partial) and pick only the XPaths you want.
  3. Commit.

Verify: Run show config running | match ironskillet in the CLI; you should see the IronSkillet tag entries (iron-skillet-version, Outbound, Inbound, Internal).

If it fails: Use Device → Setup → Operations → Revert to running configuration before committing.

Tip

Partial-load is also the right call when you want to keep the production rulebase untouched and only refresh the profile groups — select just the /config/devices/entry/.../profile-group XPath in the partial-load dialog.

5.4 Regenerate templates after pulling a branch update

Goal: Pick up upstream IronSkillet improvements.

Steps:

cd iron-skillet
git fetch origin
git checkout panos_v10_1
git pull --ff-only
# regenerate the rendered XML for distribution
sli template -sd . -ad -n skillet_full_panos_v10_1 ./out/
diff ./out/iron_skillet_panos_full.xml \
     loadable_configs/sample-mgmt-static/panos/iron_skillet_panos_full.xml

Verify: A clean git pull --ff-only plus a successful render that diffs cleanly against the in-repo loadable config.

If it fails: git status will show local edits — git stash, pull, re-render, then git stash pop to recover your variable changes.

5.5 Validate templates before pushing to a device

Goal: Catch render errors before the device sees them.

Steps:

  1. Render a candidate XML to disk: sli template -sd ./iron-skillet -ad -n skillet_full_panos_v10_1 ./out/.
  2. Lint the XML: xmllint --noout ./out/iron_skillet_panos_full.xml.
  3. Optionally push to a live device in candidate-only state:
    sli load_config -uc ./out/
    
    This places the file on the device's candidate config; you can then Preview Changes in the UI before committing.
  4. Run the IronSkillet validations against the candidate. The repo ships two validation skillets in validations/panos/: IronSkillet_assessment_panos and IronSkillet_v10x_update_panos, plus a shared report template at validations/shared_report/report.yml that produces a green/red dashboard.

Verify: xmllint returns 0; the validation-report skillet renders a dashboard with Total Checks, Checks Passed, and Checks Failed.

If it fails: Revert the candidate config: Device → Setup → Operations → Revert to running configuration.

5.6 Apply IronSkillet to Panorama (shared config)

Goal: Bring up a fresh Panorama with IronSkillet's shared device-config, log-settings, and shared rulebase.

Steps:

  1. Pick the variant that matches your topology:
    • Shared full — one Panorama, one device-group, one template-stack. Playlist: ironskillet_panorama_shared_10_1. Loadable: loadable_configs/sample-mgmt-static/panorama/iron_skillet_panorama_full.xml.
    • Not-shared full — separated DG + template stack. Playlist: ironskillet_panorama_notshared_10_1.
    • Not-shared DG-stack — DG-template-stack model only. Playlist: ironskillet_panorama_notshared_dgstack_all_10_1.
    • Shared DG-template — shared model with a DG-template. Playlist: ironskillet_panorama_shared_dgtemplate_10_1.
  2. Set STACK and DEVICE_GROUP variables (defaults are sample_stack and sample_devicegroup).
  3. Import / load the rendered XML through Panorama's UI: Panorama → Setup → Operations → Import / Load named configuration snapshot.
  4. Commit to Panorama, then Push to Devices to apply to managed firewalls.

Note

The Panorama snippets are XPath-aware: the STACK and DEVICE_GROUP variables are interpolated directly into XPath strings like /config/devices/entry[@name='localhost.localdomain']/template-stack/entry[@name='{{STACK}}']/.... If you change those variable values, the rendered XML targets the new stack / device-group automatically.

5.7 Apply only the security profiles (snippets-only path)

Goal: You don't want IronSkillet's system / management / DNS / NTP opinions. You only want the threat-prevention profiles and profile groups.

Steps:

sli template -sd ./iron-skillet -ad \
  -n ironskillet_panos_security_profiles_10_1 \
  ./out/

The resulting XML touches only:

  • /config/devices/.../vsys/entry[@name='vsys1']/tag (4 IronSkillet tags)
  • /config/.../vsys1/profiles/custom-url-category (3 lists: Block, Allow, Custom-No-Decrypt)
  • /config/.../vsys1/profiles/decryption
  • /config/.../vsys1/profiles/virus
  • /config/.../vsys1/profiles/spyware
  • /config/.../vsys1/profiles/vulnerability
  • /config/.../vsys1/profiles/file-blocking
  • /config/.../vsys1/profiles/url-filtering
  • /config/.../vsys1/profiles/wildfire-analysis
  • /config/.../vsys1/profile-group

Load via partial-load, commit, and your existing rules can immediately attach to the new profile groups.

5.8 Run IronSkillet "alert-only" against a noisy environment

Goal: Audit-mode rollout — IronSkillet's profiles set every action to alert so nothing is blocked.

Steps:

sli template -sd ./iron-skillet -ad \
  -n ironskillet_panos_alert_only_10_1 \
  ./out/

Load and commit. Watch the threat logs for a week, then flip to a real-action profile group when you're confident.

Warning

"Alert-only" is not "block-everything-zero-trust". It logs threats but does NOT drop them. Do not leave a production firewall in alert-only longer than you need to baseline traffic.

5.9 Render the set-command (CLI) version

Goal: Operators are more comfortable with PAN-OS set commands than XML.

Steps:

  1. The repo ships pre-rendered set-command files in loadable_configs/sample-set-commands/panos/iron_skillet_panos_full.conf and loadable_configs/sample-set-commands/panorama/iron_skillet_panorama_full.conf.
  2. SSH to the firewall, enter configure mode, and paste the file contents into the CLI. (Or use sli load_set -uc <file>.)
  3. Run commit.

Verify: show config diff shows the IronSkillet additions.

If it fails: revert config from configure mode.

5.10 Switch to a different PAN-OS version branch

Goal: The device runs PAN-OS 10.0 (or another version), not 10.1.

Steps:

cd iron-skillet
git branch -a | grep panos_v
git checkout panos_v10_0   # or another version branch
ls templates/panos/snippets/

The directory structure is identical across version branches; only the PAN-OS XML schema differs. The pre-rendered loadable configs in loadable_configs/ are regenerated per branch.

Important

Don't git merge between version branches. Each PAN-OS major.minor branch is a parallel snapshot, not a feature delta.

5.11 Review the variables table before rendering

Goal: Confirm what every variable does and what its default is.

Steps:

# Print the master NGFW variables block
sed -n '/^variables:/,/^snippets:/p' \
  templates/panos/snippets/.meta-cnc.yaml

# Same for Panorama
sed -n '/^variables:/,/^snippets:/p' \
  templates/panorama/snippets/.meta-cnc.yaml

Or open the Admin Guide §3 for the same data in table form.

5.12 Build an Excel spreadsheet of every variable + set-command

Goal: Hand off configuration choices to a non-Skillet operator.

Steps:

sli spreadsheet -n skillet_set_command_panos_v10_1 -o ./out/
ls ./out/
out/skillet_set_command_panos_v10_1.xlsx

The file has:

  • One row per variable.
  • A column with the rendered set-command for that variable's default.
  • Live formulas — change the variable column and the set-command column recomputes.

Distribute the spreadsheet, collect the operator's variable choices, re-render the final config.


6. Troubleshooting

Error: Default value in iron_skillet_panos_full.xlsx is wrong for sinkhole

Cause: Reported in upstream issue #177 — the spreadsheet distributed in templates/panos/set_commands/iron_skillet_panos_full.xlsx ships an outdated default for the IPv4 sinkhole. The .meta-cnc.yaml default is sinkhole.paloaltonetworks.com; the spreadsheet's hard-coded value may differ.

Solution:

  1. Treat templates/panos/snippets/.meta-cnc.yaml as the source of truth.
  2. Manually correct the IPv4 sinkhole cell in the spreadsheet to sinkhole.paloaltonetworks.com, or re-render with sli spreadsheet -n skillet_set_command_panos_v10_1 -o ./out/.

Prevention: Always cross-check the spreadsheet against the .meta-cnc.yaml defaults block before distributing it to operators.

Error: Found Skillets with errors! Please open an issue on this repository

Cause: Reported in upstream issue #172 — when Panhandler ingests the IronSkillet repository, it can flag certain skillets as malformed because the legacy snippet directories ship .meta-cnc.yaml files that reference external XML files no longer present after the move to embedded elements.

Solution:

  1. Identify which directory triggered the warning. The four legacy directories on the Panorama side are:
    • templates/panorama/snippets_dgstack_notshared/
    • templates/panorama/snippets_dgtemplate_shared/
    • templates/panorama/snippets_notshared/
  2. Use the embedded-element snippet directories instead: templates/panos/snippets/ and templates/panorama/snippets/ — both carry the full snippet bodies inside the YAML.
  3. The legacy directory READMEs explicitly state that the classic hardcoded method "will be deprecated" — so don't build new tooling against them.

Prevention: Pin Panhandler to a version that recognizes the embedded-element model, and skip importing the legacy snippet directories.

Error: commit failed: unknown element ...

Cause: PAN-OS schema mismatch — a 10.1 element is being loaded onto a device that runs 8.x or 9.x (or vice versa).

Solution:

  1. Check the firewall's PAN-OS version in Dashboard → General Information.
  2. Check out the matching IronSkillet branch: git checkout panos_v10_1 (or whichever version matches).
  3. Re-render and re-load.

Prevention: Always git-checkout the version branch that matches the target device before rendering. Issue #178 tracks the open request for 11.1 / 11.2 / 12.1 branches.

Error: password complexity check failed

Cause: The panos_ngfw_password_complexity_10_1 snippet enforces a minimum length of 12 characters, at least one uppercase, one lowercase, one numeric, one special character, blocks the username from appearing in the password, and a 24-entry history. The default adminuser password fails this policy.

Solution:

  1. Set ADMINISTRATOR_PASSWORD to a strong value at render time.
  2. If you're applying IronSkillet to an existing device, set config_admin_user=no and rotate the admin password manually.

Prevention: Never deploy with the default adminuser / adminuser credentials. The repo intentionally ships placeholder values; you are expected to override them.

Error: pip install of MarkupSafe==1.0 fails on Python 3.11

Cause: The legacy tools/archive/requirements.txt pins MarkupSafe==1.0 and PyYAML==5.4, which are incompatible with Python 3.11+ build chains.

Solution:

  1. Use the maintained SLI tool instead — pip install sli works on any modern Python.
  2. If you must run tools/archive/, downgrade to Python 3.7–3.9 in a dedicated virtualenv.

Prevention: Prefer SLI / Panhandler. The archive scripts are historical.

Error: XPath does not exist on the device

Cause: A snippet targets an XPath that the device's running config hasn't yet created — typical when loading individual snippets out of order rather than via a playlist.

Solution:

  1. Load via the playlist (ironskillet_panos_10_1, ironskillet_panorama_shared_10_1), not by hand-picking snippets in arbitrary order.
  2. If you must hand-pick, follow the dependency order in Section 4.3.

Prevention: Use playlists.

Error: email-server-profile referenced by log-forwarding does not exist

Cause: You loaded the log-forwarding profile snippet (panos_ngfw_log_settings_profiles_10_1) before the shared log-settings snippet (panos_ngfw_shared_log_settings_10_1) that creates the Sample_Email_Profile and Sample_Syslog_Profile server profiles.

Solution: Roll back, load via the full panos_full.skillet.yaml playlist (which orders them correctly), then commit.

Prevention: The dependency-ordered playlists exist precisely to prevent this. Use them.

Diagnostic decision tree

  1. Render fails? → check Python version, check that you ran git checkout on the correct version branch.
  2. Render succeeds but commit fails? → run xmllint --noout on the output; check the device's PAN-OS major.minor matches the branch.
  3. Commit succeeds but profiles are empty? → you applied snippets in the wrong order. Use the playlist.
  4. Commit succeeds, profiles exist, but logs aren't forwarded? → SYSLOG_SERVER, EMAIL_PROFILE_GATEWAY, etc. still hold defaults (192.0.2.x); set them.

Warning

Never load IronSkillet onto a production device without first previewing the candidate-config diff (Device → Setup → Operations → Save / Revert). The default rulebase is opinionated and may block traffic categories the production environment relies on.

Caution

Rendered IronSkillet XML files contain hashed admin credentials in the <phash> element. Never check a rendered config into a public repo or show it in a screenshot. Treat rendered XML and the set-command output as secret material.


7. FAQ

Q: Do I have to use Python or any tooling at all?

No. The repo ships pre-rendered, ready-to-load XML in loadable_configs/. Pick the directory that matches your scenario (sample-mgmt-static, sample-cloud-AWS, etc.), import the file via the firewall's web UI, commit. That's the lowest-friction path and it requires nothing more than git clone + a browser.

Q: What's the difference between a snippet, a skillet, and a playlist?

A snippet is a single <xpath, element> pair (one XML edit). A skillet is a .meta-cnc.yaml file that bundles many snippets and declares variables. A playlist is a .skillet.yaml in playlists/ that selects which snippets from the master collection to apply for a specific use case (alert-only, hardening-only, full, security-profiles).

Q: Which version of PAN-OS is this?

The current snapshot is PAN-OS 10.1 (branch panos_v10_1, commit 9833373). Other PAN-OS major.minor versions live on parallel branches — git branch -a | grep panos_v to list them.

Q: Will the templates work on PAN-OS 11.x or 12.x?

Issue #178 is the upstream feature request for 11.1, 11.2, and 12.1 support. As of commit 9833373, those versions are not on a maintained branch in this repository. Expect schema differences if you try to load a 10.1 config onto an 11.x device.

Q: What does the phash field do?

It's the MD5-hashed admin password. The Jinja filter md5_hash is applied to ADMINISTRATOR_PASSWORD at render time and the result is written into <phash> under /config/mgt-config/users/entry/. Treat the rendered XML as secret material — see the CAUTION above.

Q: What's INCLUDE_PAN_EDL?

A toggle that adds the predefined Palo Alto Networks External Dynamic Lists to the rulebase (known-malicious IPs, bulletproof-IP feeds, etc.). Default is no. Set to yes only on a device with a valid Threat Prevention license — the EDLs require it.

Q: Can I customize just one snippet without re-rendering everything?

Yes — render the partial playlist that contains only the snippets you care about (e.g. ironskillet_panos_security_profiles_10_1), then load it via partial-load.

Q: What does config_mgmt_intf=no actually do?

It tells the renderer to skip the management-interface IP / netmask / default-gateway snippets. Useful when applying IronSkillet to a device whose management IP is already correct — you don't want to overwrite it.

Q: Why are there four "alert-only" / "hardening" / "full" / "security profiles" playlists per platform?

Different rollout phases. alert-only = audit mode (everything logged, nothing blocked). hardening = system-config-only (no rules / no profiles). security profiles = profiles-only (no rules / no system config). full = everything. Compose them as your security maturity grows.

Q: Where do STACK and DEVICE_GROUP come from?

They're Panorama-specific variables (STACK = sample_stack, DEVICE_GROUP = sample_devicegroup by default) interpolated into XPath strings in the Panorama snippets. Set them to your environment's template-stack and device-group names before rendering.

Q: What's a "snippets_dgtemplate_shared" vs. "snippets_notshared" directory?

These are legacy Panorama snippet variants for different shared-config / template-stack topologies. Their READMEs note they will be deprecated; use templates/panorama/snippets/ (the embedded-element master) for new work.

Q: How do I roll back a botched commit?

In the firewall web UI: Device → Setup → Operations → Revert to running configuration (before commit) or Load configuration version → pick the previous saved snapshot (after commit). Save a snapshot before loading IronSkillet so you have a known-good rollback target.

Q: What's sli load_config and how does it differ from a UI import?

sli load_config -uc <file> pushes an XML file to the firewall's candidate configuration over the device API. The UI import path is identical conceptually but runs through the browser. Both put the config in pending state — you still must commit.

Q: Where can I get more detail on every variable, every snippet, every default value?

The Admin Guide (PaloAltoNetworks__iron-skillet__AdminGuide.md) has the complete variable reference table, the architecture diagram, the operations runbook, and the guidance on handling rendered configs.