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
slitool, or Panhandler / pan-cnc, or the legacy scripts intools/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/adminuserfor the firewall admin,192.0.2.xfor IPs,8.8.8.8for 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 clonediron-skilletrepo).-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), andironskillet_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.0andPyYAML==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):
- Command-line / GUI override — values typed into Panhandler or passed to SLI override everything below.
- Playlist file —
playlists/panos/*.skillet.yamlandplaylists/panorama/*.skillet.yamlcan declare variables that override the defaults from the snippets. - Snippet collection defaults —
templates/panos/snippets/.meta-cnc.yamlandtemplates/panorama/snippets/.meta-cnc.yamldefine 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_hintrules. Thedropdowntype renders as a select-box with a fixeddd_list;textis a free-form input;passwordmasks the value. The actual XML is always written either way —toggle_hintonly 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 atnoand use thepanos_security_profilesplaylist 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
xpathand anelementbody that may contain Jinja variables. Example:panos_ngfw_device_system_10_1.ironskillet_device_system_timezonewrites<timezone>UTC</timezone>to/config/devices/entry[@name='localhost.localdomain']/deviceconfig/system. Snippets are defined intemplates/panos/snippets/.meta-cnc.yamlandtemplates/panorama/snippets/.meta-cnc.yaml. - Full config (middle level). The pre-rolled-up XML in
templates/panos/full/iron_skillet_panos_full.xmlandtemplates/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.yamlinplaylists/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/, andtemplates/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.yamlandtemplates/panorama/snippets/.meta-cnc.yamlfiles 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:
- Clone the repo:
git clone https://github.com/PaloAltoNetworks/iron-skillet.git. - In the firewall web UI, go to Device → Setup → Operations.
- Click Import named configuration snapshot and upload
loadable_configs/sample-mgmt-dhcp/panos/iron_skillet_panos_full.xml. - Click Load named configuration snapshot and pick the file you just imported.
- 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.4and NTP0.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:
- Render with SLI and override only the variables you care about:
Or, easier: edit the pre-rolledsli template -sd ./iron-skillet -ad \ -n skillet_full_panos_v10_1 \ ./out/loadable_configs/sample-mgmt-static/panos/iron_skillet_panos_full.xmldirectly: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:
- Render a partial skillet using the
panos_alert_onlyorpanos_security_profilesplaylist. The first only changes the action on every threat profile toalert; 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/ - In the firewall UI, Device → Setup → Operations → Import named configuration snapshot, then Load configuration version (Partial) and pick only the XPaths you want.
- 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-groupXPath 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:
- Render a candidate XML to disk:
sli template -sd ./iron-skillet -ad -n skillet_full_panos_v10_1 ./out/. - Lint the XML:
xmllint --noout ./out/iron_skillet_panos_full.xml. - Optionally push to a live device in candidate-only state:
This places the file on the device's candidate config; you can then Preview Changes in the UI before committing.sli load_config -uc ./out/ - Run the IronSkillet validations against the candidate. The repo ships
two validation skillets in
validations/panos/:IronSkillet_assessment_panosandIronSkillet_v10x_update_panos, plus a shared report template atvalidations/shared_report/report.ymlthat 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:
- 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.
- Shared full — one Panorama, one device-group, one template-stack.
Playlist:
- Set
STACKandDEVICE_GROUPvariables (defaults aresample_stackandsample_devicegroup). - Import / load the rendered XML through Panorama's UI: Panorama → Setup → Operations → Import / Load named configuration snapshot.
- Commit to Panorama, then Push to Devices to apply to managed firewalls.
Note
The Panorama snippets are XPath-aware: the
STACKandDEVICE_GROUPvariables 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:
- The repo ships pre-rendered set-command files in
loadable_configs/sample-set-commands/panos/iron_skillet_panos_full.confandloadable_configs/sample-set-commands/panorama/iron_skillet_panorama_full.conf. - SSH to the firewall, enter
configuremode, and paste the file contents into the CLI. (Or usesli load_set -uc <file>.) - 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 mergebetween 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:
- Treat
templates/panos/snippets/.meta-cnc.yamlas the source of truth. - Manually correct the IPv4 sinkhole cell in the spreadsheet to
sinkhole.paloaltonetworks.com, or re-render withsli 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:
- 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/
- Use the embedded-element snippet directories instead:
templates/panos/snippets/andtemplates/panorama/snippets/— both carry the full snippet bodies inside the YAML. - 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:
- Check the firewall's PAN-OS version in Dashboard → General Information.
- Check out the matching IronSkillet branch:
git checkout panos_v10_1(or whichever version matches). - 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:
- Set
ADMINISTRATOR_PASSWORDto a strong value at render time. - If you're applying IronSkillet to an existing device, set
config_admin_user=noand 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:
- Use the maintained SLI tool instead —
pip install sliworks on any modern Python. - 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:
- Load via the playlist (
ironskillet_panos_10_1,ironskillet_panorama_shared_10_1), not by hand-picking snippets in arbitrary order. - 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
- Render fails? → check Python version, check that you ran
git checkouton the correct version branch. - Render succeeds but commit fails? → run
xmllint --noouton the output; check the device's PAN-OS major.minor matches the branch. - Commit succeeds but profiles are empty? → you applied snippets in the wrong order. Use the playlist.
- 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.