# iron-skillet Administrator & Developer Guide

`iron-skillet` is the Palo Alto Networks day-1 configuration baseline —
a curated set of Jinja2-templated PAN-OS XML snippets, pre-rendered full
configurations, set-command CLI scripts, and Skillet playlists that
encode hardened defaults for the NGFW and Panorama. There is no runtime,
no daemon, no API: the deliverable is **configuration**. The repo is a
build-tree of templates and the audience is the engineer who will load
the rendered output onto a Palo Alto Networks device.

This guide is for **administrators, security architects, and developers**
who want to integrate IronSkillet into a device-bring-up pipeline,
extend it with site-specific snippets, audit its security posture, or
fork it for a custom PAN-OS version.

---

## 1. Architecture Overview

IronSkillet is a **template package**, not a service. It has three
artifact tiers — snippets, playlists, and pre-rendered configs — and a
small toolchain that converts between them.

### 1.1 What it does

Given a Palo Alto Networks NGFW or Panorama in factory state,
IronSkillet produces XML (or set-command CLI text) that, when imported
and committed, configures:

- **Management-plane hardening** — login banner, password complexity
  (12-char min, complexity rules, 24-history), 5-attempt admin lockout,
  10-minute idle timeout, auto-acquire commit lock, configurable API
  key lifetime, optional SSH-only management, optional static or DHCP
  management IP.
- **System services** — DNS, NTP, timezone (`UTC`), hostname, SNMPv3
  access setting, dynamic-update schedules (threats every 30 min,
  antivirus hourly, WildFire real-time, GlobalProtect data-file hourly).
- **Threat prevention profiles** — antivirus, anti-spyware (with DNS
  sinkhole IPv4 / IPv6), vulnerability protection, file-blocking,
  URL filtering, WildFire analysis, SSL decryption profile.
- **Profile groups** — Outbound, Inbound, Internal, Alert-All, Default.
- **Custom URL categories** — `Block`, `Allow`, `Custom-No-Decrypt`.
- **Tags** — `Outbound`, `Inbound`, `Internal`, `iron-skillet-version`.
- **Rulebase scaffolding** — default security rules, sample security
  rules, decryption rules, and zone-protection profiles.
- **Logging** — shared log-settings (email, syslog, log-forwarding,
  user-id-forwarding, HIP-forwarding, GlobalProtect-forwarding,
  IP-tag-forwarding, correlation-forwarding) plus log-forwarding
  profiles for traffic / threat / WildFire / URL / data / tunnel / auth
  log types.
- **Reports & email** — simple report set, report group, email
  scheduler.
- **Panorama overlay** — same surface, applied to a configurable
  template-stack (`STACK`) and device-group (`DEVICE_GROUP`), with
  shared / not-shared / DG-template / DG-stack variants.

### 1.2 Architecture diagram

```
                        ┌──────────────────────────┐
                        │   templates/panos/       │
                        │   templates/panorama/    │
                        │   .meta-cnc.yaml         │
                        │   (snippets + variables) │
                        └────────────┬─────────────┘
                                     │
                                     │ select subset
                                     │
                  ┌──────────────────▼──────────────────┐
                  │   playlists/panos/*.skillet.yaml    │
                  │   playlists/panorama/*.skillet.yaml │
                  │   (curated playlists, "include" +   │
                  │    "when" conditionals)             │
                  └──────────────────┬──────────────────┘
                                     │
                                     │ render
                                     │
        ┌──────────────────┬─────────▼─────────┬──────────────────┐
        │                  │                   │                  │
        │  SLI tool        │  Panhandler       │  pan-cnc /       │
        │  (CLI, sli)      │  (Docker GUI)     │  legacy archive  │
        │                  │                   │  scripts         │
        └─────────┬────────┴────────┬──────────┴────────┬─────────┘
                  │                 │                   │
                  └─────────────────┼───────────────────┘
                                    │
                                    │ Jinja2 substitution + md5_hash filter
                                    │
                ┌───────────────────▼───────────────────┐
                │  Rendered output:                     │
                │   • iron_skillet_panos_full.xml       │
                │   • iron_skillet_panorama_full.xml    │
                │   • iron_skillet_panos_full.conf      │  (set commands)
                │   • iron_skillet_panos_full.xlsx      │  (variable spreadsheet)
                └───────────────────┬───────────────────┘
                                    │
                                    │ load_config / load_set / web UI import
                                    │
                ┌───────────────────▼───────────────────┐
                │  PAN-OS NGFW or Panorama device       │
                │  (candidate config → commit)          │
                └───────────────────────────────────────┘
```

### 1.3 Component inventory

| Component | Path | Role |
|-----------|------|------|
| PAN-OS snippet master | `templates/panos/snippets/.meta-cnc.yaml` | Embedded-element snippets + variable inventory for the NGFW |
| Panorama snippet master | `templates/panorama/snippets/.meta-cnc.yaml` | Same, for Panorama (shared values) |
| Panorama legacy variants | `templates/panorama/snippets_dgtemplate_shared/`, `snippets_dgstack_notshared/`, `snippets_notshared/` | Legacy skillet model (separate XML files); deprecated per their READMEs |
| Pre-rendered NGFW XML | `templates/panos/full/iron_skillet_panos_full.xml` | Full IronSkillet XML for NGFW |
| Pre-rendered Panorama XML | `templates/panorama/full/iron_skillet_panorama_full.xml` | Full IronSkillet XML for Panorama |
| Set-command output | `templates/panos/set_commands/`, `templates/panorama/set_commands/` | Pre-rendered `set` commands and `.xlsx` |
| Baseline | `templates/panos/baseline/baseline.xml`, `templates/panorama/baseline/baseline.xml` | Empty starting config (rollback target) |
| NGFW playlists | `playlists/panos/panos_*.skillet.yaml` | Alert-only, device-hardening, full, security-profiles |
| Panorama playlists | `playlists/panorama/panorama_*.skillet.yaml` | Device-hardening, notshared (full + dgstack), shared (full + dgtemplate + security-profiles) |
| Loadable configs | `loadable_configs/sample-{cloud-AWS,cloud-Azure,cloud-GCP,mgmt-dhcp,mgmt-static,set-commands}/` | Pre-rendered, ready-to-load XML/CONF for six common scenarios |
| Validations | `validations/panos/IronSkillet_assessment_panos/`, `IronSkillet_v10x_update_panos/` | Skillet-Validation reports for posture checks |
| Shared report template | `validations/shared_report/report.yml` | Validation dashboard (stats_block + table) shared across versions |
| Build script | `tools/build_all.sh` | Regenerates full templates, loadable configs, and spreadsheets |
| Submodule helper | `tools/update_submodules.sh` | Keeps cross-version submodule pointers in sync |
| Archive scripts | `tools/archive/build_all.py`, `build_full_templates.py`, `create_loadable_configs.py`, `create_set_spreadsheet.py`, `test_full_config.py`, `test_set_commands.py` | Legacy Python helpers, pre-SLI |
| Sphinx docs | `docs/conf.py`, `docs/index.rst`, `docs/notice.rst`, `docs/Makefile` | Hosted at iron-skillet.readthedocs.io |
| Submodule mounts | `submodules/`, `.gitmodules` | Cross-PAN-OS-version overlays |

### 1.4 Technology stack

| Layer | Technology | Notes |
|-------|------------|-------|
| Templating | Jinja2 (`>=2.10.1`) | `{{ VAR }}` substitution + `{% if %}`, custom `md5_hash` filter |
| Skillet schema | `.meta-cnc.yaml` (legacy) and `.skillet.yaml` (current) | Embedded-element model uses the latter |
| Rendering toolchain | SLI (`pip install sli`), Panhandler (Docker), pan-cnc | All consume the same `.meta-cnc.yaml` / `.skillet.yaml` |
| Output formats | PAN-OS XML, PAN-OS set-command CLI, Excel `.xlsx` | Spreadsheet generated via XlsxWriter |
| Documentation | Sphinx (`docs/Makefile`, `docs/conf.py`), readthedocs | Static |
| Submodule layout | git submodules per PAN-OS major.minor | `tools/update_submodules.sh` |

> [!NOTE]
> The repo's design rationale is that every PAN-OS major.minor lives on
> a parallel branch (e.g. `panos_v10_1`) instead of inside a version
> subdirectory of one branch. This keeps schema changes between PAN-OS
> versions cleanly bisectable and means a checkout at commit `9833373`
> on `panos_v10_1` shows exactly the 10.1 templates — no other version's
> XML mixed in.

### 1.5 Data flow for the main workflow

The "fresh device bring-up" flow walks left-to-right through the diagram:

1. The operator selects a playlist (e.g. `panos_full.skillet.yaml`).
2. They supply variable values (or accept defaults) via SLI / Panhandler.
3. The renderer:
   - Resolves `include:` directives — pulls each named snippet from
     `templates/panos/snippets/.meta-cnc.yaml`.
   - Evaluates `when:` clauses (e.g. `config_admin_user == 'yes'`) and
     drops snippets whose condition is false.
   - Substitutes `{{ VAR }}` placeholders, applying filters such as
     `md5_hash` for the admin password.
   - Concatenates the resulting XML elements in playlist order at the
     correct XPaths.
4. The operator imports the rendered XML via the device UI or via
   `sli load_config -uc`. The device places it in the **candidate**
   configuration.
5. `commit` activates it.

---

## 2. System Requirements

### 2.1 Target devices

| Device | Supported Versions | Notes |
|--------|-------------------|-------|
| PAN-OS NGFW | Branch `panos_v10_1` carries the **10.1** templates | Older versions live on parallel branches |
| Panorama | Same branch model — `panos_v10_1` ships Panorama 10.1 templates | Variants for shared / not-shared / DG-template / DG-stack topologies |

### 2.2 Build-host requirements

For administrators who plan to render templates on a build host (CI,
operator workstation):

| Requirement | Detail |
|-------------|--------|
| Operating system | Linux, macOS, or Windows (anywhere Python or Docker runs) |
| Python (for SLI and archive scripts) | Python 3.6+ for `sli`; Python 3.7–3.9 for the legacy `tools/archive/` scripts due to `MarkupSafe==1.0` and `PyYAML==5.4` pins |
| Docker (for Panhandler) | Any modern Docker engine that can pull `paloaltonetworks/panhandler` |
| Disk | A clone is ~50 MB; rendered output per scenario is <2 MB |
| Network | Outbound to GitHub for clone, optional outbound to the device's management IP for `sli load_config` / API push |

### 2.3 Python dependencies (legacy archive only)

`tools/archive/requirements.txt` — used only by the historical
`build_all.py` / `build_full_templates.py` / `create_loadable_configs.py`
flow:

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

> [!NOTE]
> SLI / Panhandler / pan-cnc do **not** use this requirements file —
> their dependencies are managed by their own packaging. Only run
> `pip install -r tools/archive/requirements.txt` if you specifically
> need the archived scripts.

### 2.4 Licenses needed on the device

| Skillet feature | Required PAN-OS license |
|-----------------|-------------------------|
| Antivirus profiles | Antivirus subscription |
| Anti-spyware + DNS sinkhole | Threat Prevention |
| Vulnerability profiles | Threat Prevention |
| URL filtering profiles | URL Filtering subscription |
| WildFire analysis profiles | WildFire subscription |
| SSL decryption profiles | (Decryption is included; no additional license) |
| `INCLUDE_PAN_EDL=yes` external dynamic lists | Threat Prevention |

> [!IMPORTANT]
> Loading IronSkillet on a device without the matching subscriptions
> won't break the commit, but the corresponding profile actions become
> no-ops at traffic time. The default rulebase is built around active
> subscriptions; treat the licenses as a hard prerequisite.

---

## 3. Configuration Reference

This is the complete inventory of variables, files, and snippets at
commit `9833373` on the `panos_v10_1` branch. Use it as the canonical
source of truth — when the README, the spreadsheet, or a downstream
clone disagree, the `.meta-cnc.yaml` files win.

### 3.1 Configuration files

| File | Format | Location | Purpose |
|------|--------|----------|---------|
| Snippet master (NGFW) | YAML (`.meta-cnc.yaml`) | `templates/panos/snippets/.meta-cnc.yaml` | Snippet bodies + variable inventory for PAN-OS NGFW |
| Snippet master (Panorama) | YAML | `templates/panorama/snippets/.meta-cnc.yaml` | Same, Panorama |
| Full XML output (NGFW) | XML | `templates/panos/full/iron_skillet_panos_full.xml` | Rendered full NGFW config |
| Full XML output (Panorama) | XML | `templates/panorama/full/iron_skillet_panorama_full.xml` | Rendered full Panorama config |
| Full set-command (NGFW) | text | `templates/panos/set_commands/iron_skillet_panos_full.conf` | Rendered full NGFW set-command CLI |
| Full set-command (Panorama) | text | `templates/panorama/set_commands/iron_skillet_panorama_full.conf` | Rendered full Panorama set-command CLI |
| Variable spreadsheet (NGFW) | Excel | `templates/panos/set_commands/iron_skillet_panos_full.xlsx` | Variable + set-command spreadsheet |
| Variable spreadsheet (Panorama) | Excel | `templates/panorama/set_commands/iron_skillet_panorama_full.xlsx` | Same, Panorama |
| Baseline (NGFW) | XML | `templates/panos/baseline/baseline.xml` | Rollback target for NGFW |
| Baseline (Panorama) | XML | `templates/panorama/baseline/baseline.xml` | Rollback target for Panorama |
| NGFW playlists | YAML | `playlists/panos/panos_*.skillet.yaml` | 4 playlists |
| Panorama playlists | YAML | `playlists/panorama/panorama_*.skillet.yaml` | 6 playlists |
| Loadable configs | XML / `.conf` | `loadable_configs/sample-*/` | 12 ready-to-load files (6 scenarios x 2 device types) |
| Sphinx config | Python | `docs/conf.py` | Documentation generator |
| Sphinx Makefile | Make | `docs/Makefile` (`SPHINXPROJ = iron-skillet`, `BUILDDIR = _build`) | Builds the readthedocs site |
| Build orchestrator | Bash | `tools/build_all.sh` | Regenerate full templates / loadable configs / spreadsheets |
| Submodule helper | Bash | `tools/update_submodules.sh` | Keep cross-version submodule pointers fresh |
| Archive build | Python | `tools/archive/build_all.py` | Legacy build orchestrator |
| Archive sub-builds | Python | `tools/archive/build_full_templates.py`, `create_loadable_configs.py`, `create_set_spreadsheet.py`, `test_full_config.py`, `test_set_commands.py` | Pre-SLI helpers |
| Validation report (shared) | YAML | `validations/shared_report/report.yml` | Skillet-Validation dashboard schema |
| NGFW assessment validation | YAML | `validations/panos/IronSkillet_assessment_panos/.meta-cnc.yaml` | Posture-check skillet |
| NGFW v10x update validation | YAML | `validations/panos/IronSkillet_v10x_update_panos/.meta-cnc.yaml` | Update-pre-flight skillet |
| Repo gitignore | text | `.gitignore` | |
| Submodule manifest | text | `.gitmodules` | |
| Repo metadata | Markdown | `README.md`, `CONTRIBUTING.md`, `SUPPORT.md`, `LICENSE` | |

### 3.2 NGFW variables — complete inventory

Every variable defined in `templates/panos/snippets/.meta-cnc.yaml` at
commit `9833373`. The `Type` column maps to the `type_hint:` field; the
`Toggle` column captures `toggle_hint:` parent gating.

| Variable | Type | Default | Toggle | Description |
|----------|------|---------|--------|-------------|
| `config_mgmt_intf` | dropdown (`yes`/`no`) | `no` | — | Render management-interface IP settings |
| `config_admin_user` | dropdown (`yes`/`no`) | `no` | — | Create the IronSkillet admin user |
| `config_dns` | dropdown (`yes`/`no`) | `no` | — | Render DNS settings |
| `MGMT_TYPE` | dropdown (`dhcp-client`/`static`) | `dhcp-client` | `config_mgmt_intf == yes` | Management IP mode |
| `MGMT_IP` | ip_address | `192.0.2.6` | `MGMT_TYPE == static` | Management IP |
| `MGMT_MASK` | ip_address | `255.255.255.0` | `MGMT_TYPE == static` | Management netmask |
| `MGMT_DG` | ip_address | `192.0.2.7` | `MGMT_TYPE == static` | Management default gateway |
| `DNS_1` | ip_address | `8.8.8.8` | `config_dns == yes` | Primary DNS |
| `DNS_2` | ip_address | `8.8.4.4` | `config_dns == yes` | Secondary DNS |
| `FW_NAME` | text | `panos-01` | — | Firewall hostname |
| `NTP_1` | text | `0.pool.ntp.org` | — | Primary NTP |
| `NTP_2` | text | `1.pool.ntp.org` | — | Secondary NTP |
| `API_KEY_LIFETIME` | text | `525600` | — | API key lifetime in minutes (one year) |
| `ADMINISTRATOR_USERNAME` | text | `adminuser` | `config_admin_user == yes` | Admin username |
| `ADMINISTRATOR_PASSWORD` | password | `adminuser` | `config_admin_user == yes` | Admin password (hashed via `md5_hash`) |
| `SYSLOG_SERVER` | text | `192.0.2.2` | — | Syslog server IP |
| `EMAIL_PROFILE_GATEWAY` | text | `192.0.2.1` | — | SMTP gateway for email alerts |
| `EMAIL_PROFILE_FROM` | text | `[email removed]` | — | From-address for email alerts |
| `EMAIL_PROFILE_TO` | text | `[email removed]` | — | To-address for email alerts |
| `SINKHOLE_IPV4` | text | `sinkhole.paloaltonetworks.com` | — | DNS sinkhole FQDN (anti-spyware) |
| `SINKHOLE_IPV6` | ip_address | `2600:5200::1` | — | DNS sinkhole IPv6 |
| `INCLUDE_PAN_EDL` | dropdown (`yes`/`no`) | `no` | — | Include predefined PAN External Dynamic Lists in the rulebase |

> [!IMPORTANT]
> The `ADMINISTRATOR_PASSWORD` default (`adminuser`) is a **placeholder**.
> Override it before rendering. The Jinja `md5_hash` filter writes its
> hash into the rendered XML's `<phash>` element — see Section 7.4 on
> handling rendered configs.

### 3.3 Panorama variables — complete inventory

From `templates/panorama/snippets/.meta-cnc.yaml`:

| Variable | Type | Default | Toggle | Description |
|----------|------|---------|--------|-------------|
| `config_admin_panorama` | dropdown (`yes`/`no`) | `no` | — | Render Panorama admin user |
| `config_mgmt_intf_panorama` | dropdown (`yes`/`no`) | `no` | — | Render Panorama management-IP settings |
| `PANORAMA_TYPE` | dropdown (`static`/`cloud`) | `static` | `config_mgmt_intf_panorama == yes` | Static IP or cloud (DHCP) |
| `PANORAMA_IP` | ip_address | `192.168.55.7` | `PANORAMA_TYPE == static` | Panorama mgmt IP |
| `PANORAMA_MASK` | ip_address | `255.255.255.0` | `PANORAMA_TYPE == static` | Panorama netmask |
| `PANORAMA_DG` | ip_address | `192.168.55.2` | `PANORAMA_TYPE == static` | Panorama default gateway |
| `PANORAMA_NAME` | text | `panorama01` | — | Panorama hostname |
| `DNS_1` | ip_address | `8.8.8.8` | — | Primary DNS |
| `DNS_2` | ip_address | `8.8.4.4` | — | Secondary DNS |
| `NTP_1` | text | `0.pool.ntp.org` | — | Primary NTP |
| `NTP_2` | text | `1.pool.ntp.org` | — | Secondary NTP |
| `CONFIG_EXPORT_IP` | text | `192.0.2.3` | — | Scheduled-config-export destination |
| `ADMINISTRATOR_USERNAME` | text | `adminuser` | — | Admin user |
| `ADMINISTRATOR_PASSWORD` | password | `adminuser` | — | Admin password (md5-hashed) |
| `API_KEY_LIFETIME` | text | `525600` | — | Same as NGFW |
| `EMAIL_PROFILE_GATEWAY` | text | `192.0.2.1` | — | SMTP gateway |
| `EMAIL_PROFILE_FROM` | text | `[email removed]` | — | From-address |
| `EMAIL_PROFILE_TO` | text | `[email removed]` | — | To-address |
| `SYSLOG_SERVER` | text | `192.0.2.2` | — | Syslog destination |
| `SINKHOLE_IPV4` | text | `sinkhole.paloaltonetworks.com` | — | DNS sinkhole FQDN |
| `SINKHOLE_IPV6` | ip_address | `2600:5200::1` | — | DNS sinkhole IPv6 |
| `INCLUDE_PAN_EDL` | dropdown (`yes`/`no`) | `no` | — | Include PAN EDLs |
| `STACK` | text | `sample_stack` | — | Template-stack name (interpolated into XPaths) |
| `FW_NAME` | text | `sample` | — | Device name for managed NGFW |
| `MGMT_TYPE` | dropdown (`dhcp-client`/`static`) | `dhcp-client` | — | Managed-firewall mgmt-IP mode |
| `MGMT_IP` | ip_address | `192.0.2.6` | `MGMT_TYPE == static` | Managed-firewall mgmt IP |
| `MGMT_MASK` | ip_address | `255.255.255.0` | `MGMT_TYPE == static` | Managed-firewall netmask |
| `MGMT_DG` | ip_address | `192.0.2.7` | `MGMT_TYPE == static` | Managed-firewall default gateway |
| `DEVICE_GROUP` | text | `sample_devicegroup` | — | Device-group name (interpolated into XPaths) |

### 3.4 Playlist override variables

Playlist files in `playlists/` can re-declare variables. The override
matrix:

| Playlist | Adds / overrides |
|----------|------------------|
| `playlists/panos/panos_full.skillet.yaml` | `config_mgmt_intf`, `config_admin_user`, `config_dns` (re-declared) |
| `playlists/panos/panos_alert_only.skillet.yaml` | None — uses snippet defaults |
| `playlists/panos/panos_device_hardening.skillet.yaml` | None |
| `playlists/panos/panos_security_profiles.skillet.yaml` | None |
| `playlists/panorama/panorama_shared_full.skillet.yaml` | `config_admin_panorama`, `config_mgmt_intf_panorama` |
| `playlists/panorama/panorama_notshared_full.skillet.yaml` | `config_admin_panorama`, `config_mgmt_intf_panorama`, `STACK`, `DEVICE_GROUP` |
| `playlists/panorama/panorama_notshared_dgstack.skillet.yaml` | `STACK`, `DEVICE_GROUP` |
| `playlists/panorama/panorama_shared_dgtemplate.skillet.yaml` | None |
| `playlists/panorama/panorama_shared_security_profiles.skillet.yaml` | None |
| `playlists/panorama/panorama_device_hardening.skillet.yaml` | None |

### 3.5 Snippet inventory (NGFW)

The named snippets in the master `templates/panos/snippets/.meta-cnc.yaml`,
each with its target XPath:

| Snippet (parent.element) | Target XPath |
|--------------------------|--------------|
| `panos_ngfw_device_system_mgmt_ip_10_1.ironskillet_device_system_mgmt_type` | `/config/devices/entry/deviceconfig/system` |
| `panos_ngfw_device_system_dns_10_1.ironskillet_device_system_servers` | `/config/devices/entry/deviceconfig/system/dns-setting` |
| `panos_ngfw_device_system_10_1.ironskillet_device_system_dynamic_updates` | `/config/devices/entry/deviceconfig/system` |
| `panos_ngfw_device_system_10_1.ironskillet_device_system_snmp` | `/config/devices/entry/deviceconfig/system` |
| `panos_ngfw_device_system_10_1.ironskillet_device_system_ntp` | `/config/devices/entry/deviceconfig/system` |
| `panos_ngfw_device_system_10_1.ironskillet_device_system_login_banner` | `/config/devices/entry/deviceconfig/system` |
| `panos_ngfw_device_system_10_1.ironskillet_device_system_timezone` | `/config/devices/entry/deviceconfig/system` |
| `panos_ngfw_device_system_10_1.ironskillet_device_system_hostname` | `/config/devices/entry/deviceconfig/system` |
| `panos_ngfw_device_setting_10_1.ironskillet_device_setting_ctd` | `/config/devices/entry/deviceconfig/setting` |
| `panos_ngfw_device_setting_10_1.ironskillet_device_setting` | `/config/devices/entry/deviceconfig/setting` (management/admin-lockout/idle/api/commit-lock) |
| `panos_ngfw_device_setting_10_1.ironskillet_device_setting_wildfire` | `/config/devices/entry/deviceconfig/setting` (file-size limits + verdict reporting) |
| `panos_ngfw_device_setting_10_1.ironskillet_device_setting_config` | `/config/devices/entry/deviceconfig/setting` (rematch=yes) |
| `panos_ngfw_device_setting_10_1.ironskillet_device_setting_application` | `/config/devices/entry/deviceconfig/setting` |
| `panos_ngfw_device_setting_10_1.ironskillet_device_setting_logging` | `/config/devices/entry/deviceconfig/setting` |
| `panos_ngfw_device_setting_10_1.ironskillet_device_setting_tcp` | `/config/devices/entry/deviceconfig/setting` (TCP hardening) |
| `panos_ngfw_device_setting_10_1.ironskillet_device_setting_packet_buffer` | `/config/devices/entry/deviceconfig/setting` |
| `panos_ngfw_mgt_config_users_10_1.ironskillet_mgmt_config_users` | `/config/mgt-config/users` |
| `panos_ngfw_password_complexity_10_1.ironskillet_password_complexity` | `/config/mgt-config/password-complexity` |
| `panos_ngfw_shared_log_settings_10_1.ironskillet_shared_log_settings` | `/config/shared/log-settings` |
| `panos_ngfw_tag_10_1.ironskillet_tag_outbound` / `_inbound` / `_internal` / `_ironskillet_version` | `/config/devices/entry/vsys/entry[@name='vsys1']/tag` |
| `panos_ngfw_log_settings_profiles_10_1.*` | `/config/devices/entry/vsys/entry[@name='vsys1']/log-settings/profiles` |
| `panos_ngfw_profile_custom_urlFiltering_10_1.*` | `/config/devices/entry/vsys/entry[@name='vsys1']/profiles/custom-url-category` |
| `panos_ngfw_profile_decryption_10_1.*` | `/config/devices/entry/vsys/entry[@name='vsys1']/profiles/decryption` |
| `panos_ngfw_profile_antivirus_10_1.*` | `/config/devices/entry/vsys/entry[@name='vsys1']/profiles/virus` |
| `panos_ngfw_profile_spyware_10_1.*` | `/config/devices/entry/vsys/entry[@name='vsys1']/profiles/spyware` |
| `panos_ngfw_profile_vulnerability_10_1.*` | `/config/devices/entry/vsys/entry[@name='vsys1']/profiles/vulnerability` |
| `panos_ngfw_profile_file-blocking_10_1.*` | `/config/devices/entry/vsys/entry[@name='vsys1']/profiles/file-blocking` |
| `panos_ngfw_profile_urlFiltering_10_1.*` | `/config/devices/entry/vsys/entry[@name='vsys1']/profiles/url-filtering` |
| `panos_ngfw_profile_wildfire_analysis_10_1.*` | `/config/devices/entry/vsys/entry[@name='vsys1']/profiles/wildfire-analysis` |
| `panos_ngfw_profile_group_10_1.*` | `/config/devices/entry/vsys/entry[@name='vsys1']/profile-group` |
| `panos_ngfw_rulebase_default_security_rules_10_1.*` | `/config/devices/entry/vsys/entry[@name='vsys1']/rulebase/default-security-rules` |
| `panos_ngfw_rulebase_security_10_1.*` | `/config/devices/entry/vsys/entry[@name='vsys1']/rulebase/security` |
| `panos_ngfw_rulebase_decryption_10_1.*` | `/config/devices/entry/vsys/entry[@name='vsys1']/rulebase/decryption` |
| `panos_ngfw_zone_protection_10_1.*` | `/config/devices/entry/vsys/entry[@name='vsys1']/network/profiles/zone-protection-profile` |
| `panos_ngfw_reports_simple_10_1.*` | `/config/shared/reports` (or vsys1 reports) |
| `panos_ngfw_report_group_simple_10_1.*` | `/config/shared/report-group` |
| `panos_ngfw_email_scheduler_10_1.*` | `/config/shared/email-scheduler` |

### 3.6 Hardened defaults — what the snippets actually write

Selected high-value defaults baked into the rendered XML:

| Setting | Value | Snippet |
|---------|-------|---------|
| Login banner | "You have accessed a protected system. Log off immediately if you are not an authorized user." | `ironskillet_device_system_login_banner` |
| Timezone | `UTC` | `ironskillet_device_system_timezone` |
| Threats update | every 30 min @ minute 2, threshold 48 | `ironskillet_device_system_dynamic_updates` |
| Antivirus update | hourly @ minute 4 | same |
| WildFire update | real-time | same |
| GlobalProtect data file | hourly @ minute 40 | same |
| GlobalProtect clientless VPN | hourly @ minute 50 | same |
| SNMP version | v3 only | `ironskillet_device_system_snmp` |
| Admin lockout | 5 failed attempts, 30-minute lockout | `ironskillet_device_setting` |
| Idle timeout | 10 minutes | same |
| Auto-acquire commit lock | enabled | same |
| Max rows in CSV export | 1,048,576 | same |
| Password min length | 12 | `ironskillet_password_complexity` |
| Password complexity | ≥1 upper, ≥1 lower, ≥1 numeric, ≥1 special | same |
| Password history | 24 entries | same |
| New-password differs by | 3 characters | same |
| TCP urgent-data | clear | `ironskillet_device_setting_tcp` |
| TCP drop-zero-flag | yes | same |
| TCP strip-mptcp-option | yes | same |
| Session packet-buffer protection | enabled | `ironskillet_device_setting_packet_buffer` |
| Rematch sessions on commit | yes | `ironskillet_device_setting_config` |
| Log suppression | disabled | `ironskillet_device_setting_logging` |
| Custom URL categories | `Block`, `Allow`, `Custom-No-Decrypt` | `ironskillet_custom_url_category_*` |
| Tags | `Outbound`, `Inbound`, `Internal`, `iron-skillet-version` (`version 0.0.2 for 10.1`) | `ironskillet_tag_*` |
| Sample syslog profile | `Sample_Syslog_Profile` → `Sample_Syslog`, UDP/514, BSD/`LOG_USER` | `ironskillet_shared_log_settings` |
| Sample email profile | `Sample_Email_Profile`, display `Threat_Alerts`, SMTP | same |

### 3.7 Annotated rendering walkthrough — fresh-NGFW scenario

A typical "fresh greenfield NGFW" render with custom values overrides
the toggles and a handful of identity variables:

```yaml
# my-overrides.yaml — input to SLI / Panhandler
config_mgmt_intf: 'yes'
config_admin_user: 'yes'
config_dns: 'yes'

MGMT_TYPE: static
MGMT_IP: 10.50.1.5
MGMT_MASK: 255.255.255.0
MGMT_DG: 10.50.1.1

DNS_1: 10.50.1.10
DNS_2: 10.50.1.11

FW_NAME: corp-fw-01
NTP_1: ntp1.example.com
NTP_2: ntp2.example.com

ADMINISTRATOR_USERNAME: secadmin
ADMINISTRATOR_PASSWORD: <strong-password-meeting-12-char-policy>

SYSLOG_SERVER: 10.50.1.50
EMAIL_PROFILE_GATEWAY: smtp.example.com
EMAIL_PROFILE_FROM: [email removed]
EMAIL_PROFILE_TO: [email removed]

INCLUDE_PAN_EDL: 'yes'
```

```bash
sli template -sd ./iron-skillet -ad \
  -n skillet_full_panos_v10_1 \
  ./out/
```

The renderer:

1. Loads the snippet master, resolves the `panos_full` playlist's
   include list.
2. Evaluates every `when:` clause against the overrides — three toggles
   are now `yes`, so `panos_ngfw_device_system_mgmt_ip_10_1`,
   `panos_ngfw_device_system_dns_10_1`, and
   `panos_ngfw_mgt_config_users_10_1` all render.
3. Applies the `md5_hash` filter to `ADMINISTRATOR_PASSWORD` and writes
   the result into `<phash>`.
4. Concatenates the snippet bodies in playlist order into
   `iron_skillet_panos_full.xml`.

---

## 4. Deployment & Scaling

### 4.1 Single-device bring-up

The default deployment unit is **one device, one rendered XML, one
commit**. Steps:

1. `git clone` and `git checkout` the matching `panos_v10_1` branch.
2. Render with SLI (`sli template -sd . -ad -n skillet_full_panos_v10_1 ./out/`)
   or use the pre-rendered `loadable_configs/sample-*` files.
3. Push: `sli load_config -uc ./out/iron_skillet_panos_full.xml` (or
   import via the web UI).
4. Commit on the device.

### 4.2 Fleet rollout via Panorama

The Panorama playlists (`ironskillet_panorama_shared_10_1`,
`ironskillet_panorama_notshared_10_1`, etc.) are designed to put
IronSkillet's profiles, tags, custom URL categories, log-forwarding,
and rulebase into a Panorama **device-group** + **template-stack**, so
that a single Panorama push applies the baseline to every managed
firewall.

```text
Panorama (post-IronSkillet)
├── template-stack: {{STACK}}
│   ├── deviceconfig/system  → DNS, NTP, banner, dynamic-updates, ...
│   ├── deviceconfig/setting → admin-lockout, idle-timeout, TCP hardening
│   └── mgt-config           → admin user, password complexity
├── device-group: {{DEVICE_GROUP}}
│   ├── tag                  → Outbound, Inbound, Internal, iron-skillet-version
│   ├── profiles             → virus, spyware, vulnerability, file-blocking,
│   │                          url-filtering, wildfire-analysis, decryption,
│   │                          custom-url-category
│   ├── profile-group        → outbound, inbound, internal, alert_all, default
│   ├── pre-rulebase         → security + decryption
│   └── post-rulebase        → default-security-rules
└── shared
    └── log-settings         → email, syslog, log-forwarding, userid, hip,
                               correlation, gp, ip-tag forwarding
```

### 4.3 CI/CD render pipelines

A typical pipeline:

```yaml
# .gitlab-ci.yml or GitHub Actions equivalent
stages: [render, validate, distribute]

render:
  image: python:3.9
  script:
    - pip install sli
    - git submodule update --init
    - sli template -sd . -ad -n skillet_full_panos_v10_1 ./out/
  artifacts:
    paths: [out/]

validate:
  image: python:3.9
  script:
    - apt-get update && apt-get install -y libxml2-utils
    - xmllint --noout out/iron_skillet_panos_full.xml
    - xmllint --noout out/iron_skillet_panorama_full.xml

distribute:
  script:
    - sli load_config -uc out/iron_skillet_panos_full.xml
```

### 4.4 Multi-version maintenance

When you fork IronSkillet to support a new PAN-OS version:

1. Branch from the closest existing version branch
   (`git checkout -b panos_v10_2 panos_v10_1`).
2. Update version-suffixed snippet names (`*_10_1` → `*_10_2`) and the
   skillet `name:` / `label:` fields throughout.
3. Verify XML schema compatibility against a 10.2 device — load the
   rendered candidate config and capture commit-time errors.
4. Patch any newly-required elements (10.2-only features) and remove
   anything deprecated.
5. Regenerate the loadable configs via `tools/build_all.sh`.
6. Update `submodules/` pointers via `tools/update_submodules.sh`.

> [!IMPORTANT]
> Issue **#178** is the open feature-request for 11.1 / 11.2 / 12.1
> support. Until it merges, those PAN-OS versions are not on a
> maintained branch and you'll need to fork-and-port if you must run
> them now.

### 4.5 Scaling considerations

IronSkillet is stateless build artefacts, so "scaling" mostly means:

- **Rendering throughput** — a single `sli template` invocation takes
  seconds. Even a fleet of 1000+ firewalls is bottlenecked by per-device
  commit time, not render time.
- **Distribution** — the rendered XML is <2 MB. Push via Panorama (one
  XML, fan-out to N firewalls) or via per-device `sli load_config`.
- **Variable inventory size** — keep variable count under ~50 per
  scenario; beyond that, operators struggle with the form.

---

## 5. Operations

### 5.1 Day-to-day runbook

| Task | Command / location | Frequency |
|------|--------------------|-----------|
| Pull upstream changes | `git fetch origin && git checkout panos_v10_1 && git pull --ff-only` | Monthly or on advisory |
| Re-render after pull | `sli template -sd . -ad -n skillet_full_panos_v10_1 ./out/` | After every pull |
| Sync submodules | `bash tools/update_submodules.sh` | After version-branch updates |
| Regenerate loadable_configs/ | `bash tools/build_all.sh` | Before publishing a tag |
| XML lint | `xmllint --noout out/*.xml` | After every render |
| Deploy to firewall | `sli load_config -uc out/iron_skillet_panos_full.xml` | Per device bring-up |
| Deploy to Panorama | UI import + commit + push to devices | Per fleet change |
| Validate posture | Run `IronSkillet_assessment_panos` skillet via SLI | After deploy + quarterly |
| Pre-update check | Run `IronSkillet_v10x_update_panos` skillet | Before PAN-OS upgrade |

> [!NOTE]
> The two validation skillets in `validations/panos/` produce a green/red
> dashboard via `validations/shared_report/report.yml`. The shared
> report's `pre_processing` Jinja block tallies `pass` / `fail` /
> `total` for the operator.

### 5.2 Monitoring

There is no IronSkillet runtime to monitor. Monitor the device that
IronSkillet was loaded onto:

- **Syslog target** (the value of `SYSLOG_SERVER`) should show traffic,
  threat, WildFire, URL, data, tunnel, and auth log streams.
- **Email destination** (`EMAIL_PROFILE_TO`) should receive the
  `Email_Critical_System_Logs` and `Email_Malicious_Verdicts` alerts.
- **Scheduled config exports** (Panorama only, `CONFIG_EXPORT_IP`)
  should land at the SCP/FTP target on schedule.

### 5.3 Backup & rollback

Always save a snapshot **before** loading IronSkillet:

```text
Device → Setup → Operations → Save named configuration snapshot
  → name: pre-ironskillet-YYYYMMDD
```

Roll back via:

```text
Device → Setup → Operations → Load named configuration snapshot
  → pick the pre-ironskillet snapshot → Commit
```

Or load the in-repo `templates/panos/baseline/baseline.xml` for a
factory-style empty baseline.

> [!TIP]
> A two-step rollback is more forgiving: load the snapshot, run
> **Save candidate configuration**, then commit. That gives you one
> more "Revert to running configuration" exit before you're locked
> into the rollback.

### 5.4 Audit & evidence

Iron-skillet writes an explicit version tag at
`/config/devices/entry/vsys/entry[@name='vsys1']/tag/entry[@name='iron-skillet-version']`
with comment `version 0.0.2 for 10.1 - version of this IronSkillet
template file`. Use it as audit evidence:

```bash
ssh admin@firewall 'show config running' | grep iron-skillet-version
```

### 5.5 Common operations checklist

- [ ] Cloned the repo at the correct branch (`panos_v10_1`).
- [ ] Submodules updated (`tools/update_submodules.sh`).
- [ ] Variables overridden — particularly `ADMINISTRATOR_PASSWORD`,
      `SYSLOG_SERVER`, `EMAIL_PROFILE_*`, `FW_NAME`, NTP/DNS.
- [ ] Rendered output xmllint-clean.
- [ ] Pre-load snapshot saved on the device.
- [ ] Loaded as candidate config; **previewed** the diff in the UI.
- [ ] Committed.
- [ ] Validation skillet run and dashboard green.
- [ ] Rendered XML stored in a controlled location (encrypted, ACL'd).
- [ ] Admin password rotated post-commit; API key regenerated.

---

## 6. Performance

IronSkillet is a build-time tool — the "performance" axis is render
time and the impact the rendered config has on the device.

### 6.1 Render performance

| Metric | Typical value |
|--------|---------------|
| `sli template` for `skillet_full_panos_v10_1` | <5 seconds on a modern laptop |
| `sli spreadsheet` for `skillet_set_command_panos_v10_1` | <10 seconds (Excel write is the long pole) |
| Full `tools/build_all.sh` rebuild | <60 seconds (regenerates 12 loadable configs + 2 spreadsheets) |
| Repo clone | <30 seconds on a typical broadband connection |

### 6.2 Device-side impact

The rendered config has measurable effects on device behavior:

| Setting | Effect |
|---------|--------|
| Idle timeout = 10 minutes | Web-UI sessions log out faster than the PAN-OS default |
| Auto-acquire commit lock | Two concurrent admins can't both commit |
| Admin lockout 5 attempts / 30 min | Brute-force attempts get locked out quickly; legitimate typo-prone admins also get locked |
| Dynamic updates every 30 min (threats) | Slightly higher mgmt-plane CPU than the PAN-OS default |
| Packet-buffer protection enabled | Slightly higher dataplane CPU; protects against PBP exhaustion |
| Rematch sessions on commit | Existing sessions re-evaluated against the new rulebase — useful for security, but disruptive if a rulebase change blocks a category that was previously allowed |
| Log suppression off | Higher syslog and management-plane log volume |

> [!NOTE]
> The "rematch sessions on commit" setting is a deliberate IronSkillet
> choice: when you push a new rulebase, every existing flow is re-tested.
> That makes IronSkillet's security stance "fail closed on commit" — but
> it also means a too-aggressive commit can drop established connections.

### 6.3 Storage on the device

| Item | Approximate size |
|------|------------------|
| Loaded XML | <2 MB |
| Per-day log volume (with all profiles + log-forwarding) | Highly site-dependent; typical 10–500 MB/day for a medium branch |

---

## 7. Security

IronSkillet's reason for existing is security hardening. This section
covers the security controls the rendered config enforces, the secrets
the rendered config contains, and the operational steps required to
keep both safe.

### 7.1 Authentication & authorization

The rendered config writes:

- A **superuser** admin entry under `/config/mgt-config/users` when
  `config_admin_user == 'yes'`. The username is `ADMINISTRATOR_USERNAME`,
  the password is the **MD5 hash** of `ADMINISTRATOR_PASSWORD` (Jinja
  filter `md5_hash`), stored in `<phash>`.
- A password-complexity policy at `/config/mgt-config/password-complexity`:
  length 12, at least one upper / lower / numeric / special, blocked
  username inclusion, 24-entry history, must differ by 3 characters from
  the previous password.
- An admin-lockout policy at
  `/config/devices/entry/deviceconfig/setting/management/admin-lockout`:
  5 failed attempts, 30-minute lockout.
- An idle-timeout of 10 minutes for management sessions.
- An API-key lifetime of `API_KEY_LIFETIME` minutes (default 525600 =
  one year). API keys minted by the device after the load are bounded
  by this lifetime.

### 7.2 Input validation

IronSkillet's variable forms enforce typing at render time
(`type_hint: ip_address` rejects non-IP input in Panhandler, dropdowns
constrain values to `dd_list` members). The renderer fails fast if an
unbound variable appears in a Jinja substitution.

### 7.3 Secrets handling

The repo itself does **not** carry production secrets. Defaults are
deliberate placeholders (`adminuser` / `adminuser`, `192.0.2.x`,
`8.8.8.8`). The expectation is that the operator overrides them at
render time via SLI / Panhandler.

> [!IMPORTANT]
> Never commit a rendered IronSkillet XML file with real credentials or
> real management IPs back to a public repository. The defaults are
> public; the moment you override them, the rendered output becomes
> sensitive.

### 7.4 Treat rendered configurations as sensitive

IronSkillet's deliverable is a rendered PAN-OS configuration. The rendered XML is not sanitized: it carries the admin
password hash (`<phash>`, written by the `md5_hash` filter from `ADMINISTRATOR_PASSWORD`), and once the device is in
service the saved configuration also holds whatever the operator adds later, such as downstream service credentials
(LDAP, RADIUS, TACACS+, SNMP, syslog) and certificates.

Handle rendered and saved configurations as credential material:

1. **Set a strong `ADMINISTRATOR_PASSWORD`** at render time (the default `adminuser` is a placeholder).
2. **Restrict ACLs on saved XML / set-command output** (POSIX `0600`, NTFS owner-only) and encrypt it at rest if
   it sits on shared storage.
3. **Rotate API keys** minted during the build pipeline at the end of the deployment window, and keep
   `API_KEY_LIFETIME` no longer than you need.
4. **Never commit or share** a rendered configuration with real values outside a need-to-know channel; remove
   `<phash>`, server-profile credentials and certificate blocks first.

### 7.5 Network security

The rendered config enforces:

- **SNMPv3 only** — `snmp-setting/access-setting/version/v3`. SNMPv1/v2c
  are disallowed.
- **TCP hardening** — `urgent-data: clear`, `drop-zero-flag: yes`,
  `check-timestamp-option: yes`, `strip-mptcp-option: yes`.
- **Session packet-buffer protection** enabled.
- **Default zone-protection profile** wired into the zone-protection
  snippet at
  `/config/devices/.../network/profiles/zone-protection-profile`.
- **Default rulebase** including the IronSkillet "default-security-rules"
  override and a curated security/decryption rulebase.

### 7.6 Logging & audit

The rendered config wires every log type — traffic, threat, WildFire,
URL, data, tunnel, auth — to the `Sample_Syslog_Profile` (UDP/514, BSD
format, `LOG_USER` facility) and emails critical system logs and
malicious WildFire verdicts via the `Sample_Email_Profile`. Log
suppression is **disabled** (`<log-suppression>no</log-suppression>`)
so that nothing gets aggregated away. The shared log-settings cover
config, system, user-id, HIP, GlobalProtect, IP-tag, and correlation
forwarding.

### 7.7 PAN-OS subscriptions

Section 2.4 already lists the licenses required for the threat profiles
to act. From a security-control perspective: **the rendered profiles
exist regardless of license**, but the device cannot enforce them
without the relevant subscription. Verify subscriptions before relying
on the IronSkillet baseline as a security control.

> [!WARNING]
> A profile group attached to a rule on a device without a Threat
> Prevention license logs a license-warning at commit time but does
> not block the commit. The rule will pass traffic without
> threat inspection. Always confirm `request license info` shows the
> required subscriptions before attesting that IronSkillet is enforced.

### 7.8 Hardening checklist (operator-side)

- [ ] **Restricted ACLs on rendered configurations**: every rendered
      `iron_skillet_*_full.xml` and `iron_skillet_*_full.conf` is
      stored on owner-only filesystem permissions (POSIX `0600` /
      NTFS owner-only) and never checked into a shared or public repo.
      Versioned-config storage encrypts at rest.
- [ ] **Post-deploy API key rotation cadence**: every IronSkillet load
      triggers an immediate API key rotation for the device, and a
      recurring rotation no less often than the configured
      `API_KEY_LIFETIME`. API keys minted during the build pipeline
      are revoked at the end of the deployment window.
- [ ] **Downstream credential rotation policy**: LDAP bind, RADIUS
      shared secret, TACACS+ key, SNMP community / SNMPv3 user, and
      syslog credentials that are added to the IronSkillet-derived
      configuration are rotated on the same cadence as the rendered
      config (or on every deploy, whichever is shorter), and old
      credentials are revoked at their respective directory / IdP /
      SIEM.

---

## 8. Developer Internals

This section is the contributor's view: how the templates are
structured, how to add a new snippet, how to add a new PAN-OS version,
and how the test pattern works.

### 8.1 Repository structure

```
iron-skillet/
├── README.md
├── CONTRIBUTING.md
├── SUPPORT.md
├── LICENSE
├── .gitignore
├── .gitmodules
├── docs/                    Sphinx site (readthedocs)
│   ├── conf.py
│   ├── index.rst
│   ├── notice.rst
│   └── Makefile
├── templates/               Snippet masters + pre-rendered output
│   ├── README.md
│   ├── panos/
│   │   ├── snippets/.meta-cnc.yaml          (HIGH triage, 112209c)
│   │   ├── full/                            (rendered XML)
│   │   ├── set_commands/                    (rendered set-command)
│   │   └── baseline/baseline.xml
│   └── panorama/
│       ├── snippets/.meta-cnc.yaml          (HIGH, 134929c)
│       ├── snippets_dgtemplate_shared/      (legacy, README marked deprecated)
│       ├── snippets_dgstack_notshared/      (legacy)
│       ├── snippets_notshared/              (legacy)
│       ├── full/
│       ├── set_commands/
│       └── baseline/baseline.xml
├── playlists/
│   ├── README.md
│   ├── panos/                               (4 .skillet.yaml files)
│   └── panorama/                            (6 .skillet.yaml files)
├── loadable_configs/        12 ready-to-load files (6 scenarios x 2 device types)
├── validations/             Validation skillets + shared report
│   ├── README.md
│   ├── panos/
│   │   ├── IronSkillet_assessment_panos/
│   │   └── IronSkillet_v10x_update_panos/
│   └── shared_report/report.yml
├── tools/                   Build orchestration
│   ├── README.md
│   ├── build_all.sh
│   ├── update_submodules.sh
│   ├── .gitignore
│   └── archive/             Pre-SLI Python helpers (deprecated path)
└── submodules/              Cross-version submodule mounts
```

### 8.2 Adding a new snippet to an existing version

1. Edit `templates/panos/snippets/.meta-cnc.yaml` (or the Panorama
   master). Append a new entry under `snippets:`:
   ```yaml
   - name: panos_ngfw_<group>_10_1.ironskillet_<descriptive_name>
     xpath: /config/devices/entry[@name='localhost.localdomain']/...
     element: |-
         <your-element>
           <child>{{ YOUR_VAR }}</child>
         </your-element>
     cmd: set
     when: <optional Jinja predicate>
   ```
2. If the snippet introduces a new variable, add it to the `variables:`
   block at the top of the same file with `name`, `description`,
   `default`, `type_hint`, optional `dd_list`, optional `toggle_hint`,
   and `help_text`.
3. Add the snippet's `name` to the relevant playlists in
   `playlists/panos/`. Place it in **dependency order** — after any
   snippets it depends on, before any that depend on it.
4. Regenerate the full XML, set-commands, and loadable configs:
   ```bash
   bash tools/build_all.sh
   ```
5. Run the validation skillet to confirm the rendered output still
   passes posture checks.

### 8.3 The snippet-vs-full-config decision

When deciding whether a new feature should be a snippet or live in a
hand-rolled full XML:

- **Snippet** if it's a reusable unit that any playlist might want to
  include or exclude (e.g. a new threat profile, a new tag).
- **Full config** is **derived**. Don't hand-edit the `full/` XML — it's
  rebuilt by the renderer from the snippets via `tools/build_all.sh`.

### 8.4 Adding a new PAN-OS version

1. Branch from the closest existing version branch:
   `git checkout -b panos_v10_2 panos_v10_1`.
2. Find-and-replace the version suffix throughout — every snippet
   `name`, every parent skillet `name:` / `label:`, every playlist
   `include:` reference (`*_10_1` → `*_10_2`).
3. Build a candidate render against a 10.2 device; capture commit
   errors. PAN-OS 10.2 may have schema additions (new XML elements) or
   removals.
4. Patch each snippet whose schema changed.
5. Regenerate via `tools/build_all.sh`.
6. Update `submodules/` pointers via `tools/update_submodules.sh`.
7. Tag the branch.

> [!NOTE]
> The repo's README explicitly does not document a programmatic
> migration helper between versions — it's a human-driven port. Issue
> **#178** (request for 11.1 / 11.2 / 12.1 branches) is outstanding;
> contributors who port to those versions can submit the work as a new
> branch via the standard CONTRIBUTING.md flow.

### 8.5 How variables resolve at render time

Render-time variable resolution is a **layered lookup**:

1. The renderer reads the master `.meta-cnc.yaml`'s `variables:` block —
   that's the schema.
2. It overlays the playlist's `variables:` block (if any) — playlist
   declarations override snippet defaults.
3. It overlays the operator's input (CLI args, Panhandler form, YAML
   override file) — operator input wins.
4. For each snippet selected by the playlist, it evaluates the `when:`
   clause against the merged variable map. Falsy → snippet skipped.
5. Surviving snippets have their `element:` body run through Jinja2
   with the merged variable map. Filters used in the repo: `md5_hash`
   (exactly one use, the admin password) and the standard Jinja
   filter set.
6. Rendered snippet XML is concatenated at the snippet's `xpath`.

### 8.6 Testing pattern

Two test layers exist in this commit:

- **Static**: `xmllint --noout out/iron_skillet_panos_full.xml` runs as
  part of the build to confirm the rendered XML parses.
- **Validation skillets**: `validations/panos/IronSkillet_assessment_panos/`
  and `validations/panos/IronSkillet_v10x_update_panos/` ship as
  Skillet-Validation packages. Run them via SLI against a candidate
  configuration; they emit a YAML report consumed by
  `validations/shared_report/report.yml` to produce a green/red dashboard
  with `Total Checks`, `Checks Passed`, and `Checks Failed` counters.
- **Legacy archive tests**: `tools/archive/test_full_config.py` and
  `test_set_commands.py` exist but are part of the deprecated archive
  pipeline; new contributors should rely on the validation skillets and
  `xmllint`.

### 8.7 Code patterns and conventions

- **Snippet naming**: `panos_ngfw_<area>_<version>.ironskillet_<thing>`.
  The first half identifies the playlist that owns the snippet; the
  `ironskillet_*` half identifies the specific element within. The
  version suffix is **load-bearing** — the renderer uses it to dispatch
  to the correct branch's snippet master.
- **XPath style**: Always rooted at `/config/devices/entry[@name='localhost.localdomain']/...`
  for NGFW. Panorama snippets that touch a managed device's config wrap
  the path in `/config/devices/entry[@name='localhost.localdomain']/template-stack/entry[@name='{{STACK}}']/.../localhost.localdomain/...`.
- **`element:` style**: YAML literal block (`|-`) preserves whitespace.
  Indent the XML body two spaces under the literal-block marker.
- **`when:` style**: Quote string comparisons (`config_admin_user == 'yes'`).
  Keep the comparison fully on one line.
- **Variable names**: ALL_CAPS for substituted values, lower_snake for
  toggles (`config_*`).

### 8.8 Potential improvements

Areas where contributions are welcome:

- **Fix the spreadsheet sinkhole default** (issue **#177**) — the
  `iron_skillet_panos_full.xlsx` ships an outdated default for the
  IPv4 sinkhole; sync it with `templates/panos/snippets/.meta-cnc.yaml`.
- **Resolve Panhandler ingestion warnings** (issue **#172**) — some
  legacy snippet directories still ship `.meta-cnc.yaml` referencing
  external XML files; either modernize to embedded-element or remove.
- **Add maintained 11.1 / 11.2 / 12.1 branches** (issue **#178**).
- **Expand the validation skillets** to cover every IronSkillet element,
  not just the high-level posture indicators.
- **Replace `MarkupSafe==1.0` and `PyYAML==5.4` pins** in
  `tools/archive/requirements.txt` so the archive scripts can run on
  Python 3.11+.

### 8.9 Contributing guide

The repo's `CONTRIBUTING.md` is the canonical contributor onboarding.
At a high level: branch, edit, render, lint, validate, PR against the
relevant version branch (don't PR against `main`).

---

## 9. Integration Points

iron-skillet is a build artifact, so "integration" means: which tools
consume it, which devices receive it, and which systems are downstream.

### 9.1 Skillet-runner integrations

| Tool | Role | Consumes |
|------|------|----------|
| **SLI** ([`gitlab.com/panw-gse/as/sli`](https://gitlab.com/panw-gse/as/sli)) | Maintained CLI runner | `templates/*/snippets/.meta-cnc.yaml`, `playlists/*/*.skillet.yaml` |
| **Panhandler** ([live.paloaltonetworks.com](https://live.paloaltonetworks.com/t5/Skillet-Tools/Install-and-Get-Started-With-Panhandler/ta-p/307916)) | Docker GUI | Same |
| **pan-cnc** | Programmatic rendering | Same |
| **`tools/archive/build_full_templates.py`** | Legacy in-repo build | `templates/*/snippets/.meta-cnc.yaml` |

### 9.2 Device integrations

| Target | Mechanism | Driver |
|--------|-----------|--------|
| PAN-OS NGFW | XML import via `Device → Setup → Operations` | Manual or `sli load_config -uc` |
| PAN-OS NGFW | Set-command paste in `configure` mode | Manual or `sli load_set -uc` |
| Panorama | XML import via `Panorama → Setup → Operations` | Manual or `sli load_config -uc` |
| Panorama-managed firewalls | Push from Panorama | Panorama's commit-and-push |

### 9.3 Downstream system integrations (after device load)

The rendered config wires the device into your environment:

| Downstream system | Variable | Default | Used in |
|-------------------|----------|---------|---------|
| Syslog SIEM | `SYSLOG_SERVER` | `192.0.2.2` | `Sample_Syslog_Profile` UDP/514 BSD/`LOG_USER` |
| SMTP | `EMAIL_PROFILE_GATEWAY`, `EMAIL_PROFILE_FROM`, `EMAIL_PROFILE_TO` | `192.0.2.1` / `[email removed]` / `[email removed]` | `Sample_Email_Profile` for critical system + WildFire verdict alerts |
| NTP | `NTP_1`, `NTP_2` | `0.pool.ntp.org` / `1.pool.ntp.org` | Device clock |
| DNS | `DNS_1`, `DNS_2` | `8.8.8.8` / `8.8.4.4` | Device resolution |
| DNS sinkhole | `SINKHOLE_IPV4`, `SINKHOLE_IPV6` | `sinkhole.paloaltonetworks.com` / `2600:5200::1` | Anti-spyware DNS-sinkhole |
| Palo Alto threat content | (implicit) | `updates.paloaltonetworks.com` | Dynamic-update schedule |
| Scheduled config export (Panorama) | `CONFIG_EXPORT_IP` | `192.0.2.3` | Panorama config snapshot destination |

### 9.4 Validation reporting integration

The `validations/shared_report/report.yml` is the integration surface
for Skillet-Validation viewers: it defines a stats block (Total /
Passed / Failed) and a results table that any
Skillet-Validation-compatible viewer (Panhandler, custom dashboards)
can render. The `pre_processing` Jinja block is the data-shaping
contract: viewers feed it raw `data.<check>` results and receive a
`{checks: [...], stats: {...}}` JSON object back.

### 9.5 Cross-version submodule integration

`.gitmodules` and `tools/update_submodules.sh` define how this branch
references sibling PAN-OS version branches. The `submodules/` directory
acts as a mount point for cross-version overlays — useful when a
multi-version build job needs all branches reachable from one working
tree.

```bash
# typical multi-version setup
git clone https://github.com/PaloAltoNetworks/iron-skillet.git
cd iron-skillet
bash tools/update_submodules.sh
ls submodules/   # version-branch mounts
```

> [!TIP]
> If you maintain a private fleet that runs multiple PAN-OS versions
> simultaneously (mid-upgrade phase), keep one IronSkillet checkout per
> version branch in CI rather than relying on submodules — the
> submodule layout shines when one operator workflow needs cross-version
> visibility, not when CI bakes a single version's artifacts.

### 9.6 External documentation

The repo links out to:

- [Iron-Skillet readthedocs](https://iron-skillet.readthedocs.io/) —
  the Sphinx-rendered docs site (built from `docs/`).
- [Variables list and descriptions](https://iron-skillet.readthedocs.io/en/docs_master/creating_loadable_configs.html#variables-list-and-descriptions)
  — the upstream variable reference (this document covers the same
  data in §3).
- [Palo Alto Networks Live community](https://live.paloaltonetworks.com/) —
  Panhandler installation guide and skillet ecosystem.
- [SLI repository (GitLab)](https://gitlab.com/panw-gse/as/sli) —
  upstream for the maintained skillet runner.

---

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