# iron-skillet User Guide

`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.

```bash
git clone https://github.com/PaloAltoNetworks/iron-skillet.git
cd iron-skillet
ls loadable_configs/
```

```text
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:

```bash
# 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`](https://gitlab.com/panw-gse/as/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.

```bash
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
```

```text
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.

```bash
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:

```bash
sli template -sd ../iron-skillet -ad \
  -n skillet_full_panos_v10_1 \
  ../my-output-dir
```

```text
[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](https://live.paloaltonetworks.com/t5/Skillet-Tools/Install-and-Get-Started-With-Panhandler/ta-p/307916)
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.

```bash
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.

```bash
sli spreadsheet -n skillet_set_command_panos_v10_1 -o ./out/
```

```text
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.

```bash
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:

```bash
git -C iron-skillet log -1 --oneline
sli --version
```

```text
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.

```yaml
# 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.

```yaml
# 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`.

```yaml
# 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:
   ```bash
   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:
   ```bash
   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.
   ```bash
   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:**

```bash
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:
   ```bash
   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:**

```bash
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:**

```bash
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:**

```bash
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:**

```bash
# 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:**

```bash
sli spreadsheet -n skillet_set_command_panos_v10_1 -o ./out/
ls ./out/
```

```text
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.

---

_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._
