Skillet Builder Documentation¶
Skillet Builder Overview¶
Welcome. This site contains Skillet Builder documentation, examples, and tutorials designed to expand the Builder community. This is a living set of content updated as new skillet types, examples, and tutorials are available.
This video contains a quick Skillet overview and a few demonstrations playing Skillets with Panhandler.
The purpose of Skillets is to capture knowledge and expertise into sharable units that can be consumed by users to reduce complexity. This can complement or remove the requirement to capture GUI config guides that can require hours of configuration steps, often with all users entering the same sets of data. Thus the transition from ‘show me how to configure’ to ‘give a configuration to load’.
Skillet Use Cases¶
Skillets are built and played to (1) simplify the burden of repeatable tasks and (2) rapidly get to an outcome using recommended practices from subject matter experts. These goals can be extended over a wide array of use cases:
Deploy¶
Instead of manually deploying infrastructure, skillets help with automated or semi-automated tasks. Integrating with Terraform and Ansible, skillets can provide a web UI interface to capture data and run templates and playbooks.
instantiate compute resources in the cloud
simplify licensing
update device software
perform NGFW threat and AV content updates
install plug-ins
Configure¶
Save time by levering best practice and reference configurations created by subject matter experts.
Day One best practice configurations
one-click demo and POC setup
mobile workforce remote access
SecOps recommended reports and custom signatures
vertical-specific use cases
quick config guides as skillets
Assess¶
Gain visibility about system and configuration state.
PAN-OS and Panorama configuration validation checks
automated Security Lifecycle Review (SLR)
query service information such as Prisma Access service info
Skillet Players¶
Skillets are open-source and extensible, ready to play using any supported application.
Supported players and utilities include:
Skillet Implementation¶
Skillets are implemented using the ‘Command’ Design Pattern. This allows all aspects of working with APIs to be encapsulated behind a clean, single method interface. This significantly reduces the complexity of building automation tooling that often requires access to many different APIs, vendor devices, and systems.
The Skillet Framework¶
The Skillet Framework is designed to create a structured yet extensible model for a multitude of skillet types.
View the video for a quick overview of the Skillet Framework
Github¶
Github is a widely known version-control and collaboration platform. Typically used by software developers, it provides the perfect solution as a skillet archive. The key elements of Github include:
easy to push and archive skillet content
simple import to ingest and play skillets
branching for development and software version requirements
forking for community development and customization
issue tracking to provide skillet feedback
Other sites and platforms that mimic Github can also be used to archive and share skillets.
Github Repositories¶
Repositories (repos) are used to capture a set of skillets. Any new skillet author will require a Github account and each project will have its own repo.
Github Branches¶
Each repo can have one or more branches. Branches are used to isolate development without affecting other branches.
The primary branch is master
with optional branches using names such as develop
or panos_v9.1
.
These optional branches are named according to their purpose for skillet development or software version specific skillets.
Each branch contains:
the skillet folders
a common README.md file including documentation, authors, and the support policy
LICENSE file
[optional] .gitignore file to name files and directories not pushed to Github
Skillet Folders¶
Each skillet is stored in its own directory containing:
the .meta-cnc.yaml file with core skillet content
README.md file with skillet specific documentation
[optional] associated files based on skillet type
Skillet Metadata File¶
The skillet metafile contains key descriptors, variable definitions, and skillet actions.
Preamble¶
The opening section of the skillet yaml file containing contextual data about the skillet.
Includes:
name: unique skillet name
label: contextual text used in applications for skillet selection
description: short description of the skillet and any prerequisites
type: the type of skillet such as panos, panorama, pan_validation
collection: one or more tags for skillet grouping
optional metadata tags
Variables¶
Variables used in the skillet are entered as part of a web form or inherited from prior played skillets.
This allows the Skillet Builder to determine what variables are used within the skillet, use form validation to ensure proper formatting is used for each variable, and use dynamic elements such as hide/show to provide display controls.
A broad set of Variable Types are available.
Snippets¶
This is the action part of the skillet and unique for each skillet type.
panos/panorama: reads a list of XPaths and elements that are pushed to the device for configuration
template: simple rendering of a text file displayed to the screen
rest: a series of REST API interactions including response capture
python3: run a python script in a local virtual environment
pan_validation: assess a configuration against a set of predefined rules
terraform: run terraform deployments for cloud deployments
docker: instantiate a docker container to run virtual applications
SkilletBuilder Feedback¶
Feedback on all aspects of SkilletBuilder and this documentation encouraged and welcome. Feel free to submit an issue on GitHub with suggestions, descriptions of bugs, or feature requests.
Support Policy¶
The code and templates in the repo are released under an as-is, best effort, support policy. These scripts should be seen as community supported and Palo Alto Networks will contribute our expertise as and when possible. We do not provide technical support or help in using or troubleshooting the components of the project through our normal support options such as Palo Alto Networks support teams, or ASC (Authorized Support Centers) partners and backline support options. The underlying product used (the VM-Series firewall) by the scripts or templates are still supported, but the support is only for the product functionality and not for help in deploying or using the template or script itself. Unless explicitly tagged, all projects or work posted in our GitHub repository (at https://github.com/PaloAltoNetworks) or sites other than our official Downloads page on https://support.paloaltonetworks.com are provided under the best effort policy.
Skillet Types¶
The original skillets were focused on configuration using XML snippets. This is now extended to include a broad array of skillet types for deployment, validation, operations, and other needs beyond configuration.
docker¶
With a docker skillet, you can use any available libraries in the Docker image. This allows you to distribute custom tools and scripts, or use existing dockerized tools, as a skillet.
Using a docker skillet, you can create a single Docker image that contains all your dependencies and distribute that with the Skillet metadata file.
Use cases:
Ansible playbooks and associated libraries
Terraform implementations
Shell and Python scripts
View examples of docker skillets
panorama¶
Used for API-based XML configuration and operational interactions with Panorama.
Examples:
Push XML configuration snippets that merge into the candidate configuration
Operational commands to generate certificates or perform load config partial
Configuration commands for move, edit, and delete
View examples of panorama skillets
Note
The panos and panorama types are functionally identical and used primarily to denote the platform target for the skillet.
Note
The panorama and panorama-gpcs [Prisma Access] skillet types are identical, except for tool handling of the commit models. The panorama type will only commit to Panorama while the panorama-gpcs type will also push the configuration to Prisma Access.
panorama-gpcs¶
Used for API-based XML configuration and operational interactions with Panorama specific to Prisma Access plug-in configurations.
Examples:
Standard Panorama configuration for templates, template-stacks, and device-groups
Plug-in configuration for service connections, remote networks, and mobile users
View examples of Prisma Access skillets
panos¶
Used for API-based XML configuration and operational interactions with a PAN-OS NGFW.
Examples:
Push XML configuration snippets that merge into the candidate configuration
Operational commands to generate certificates or perform ‘load config partial’
Configuration commands for move, edit, and delete
View examples of panos skillets
pan_validation¶
Used to capture and parse XML configuration file and operational command outputs and match against a set of Boolean test rules.
Examples:
Best practice configuration assessments (eg. IronSkillet)
Dependency checks before loading configuration skillets
Check for potential merge conflicts based on existing config elements
Troubleshooting assistance with config/system insights
View examples of template skillets
python3¶
Run Python scripts within a controlled virtual environment and include a web UI instead of command line arguments. Designed to simplify sharing of Python scripts.
Current version used in PanHandler is python3.6
Examples:
Perform content updates
Use the NGFW and Support APIs to generate an SLR
Generate and import configuration files to a device
View examples of python skillets
Note
Python scripts are useful when checking system state is required. The best example is checking job status for a process before performing the next task. Some skillets are stateless and do not have this capability.
rest¶
General purpose REST interactions with any REST-supported API. Used to view full results or to capture to use as input variables in other skillets.
Examples:
Prisma Access or other platform service information
Query a device and return a list of values used in a skillet UI dropdown
Check status of cloud platforms
View examples of rest skillets
template¶
This general purpose skillet type takes a text file input and renders output to screen after variable substitutions.
Examples:
Full XML config file generation for manual imports
Set command outputs
Third party text file generation as reference configurations
Skillet workflow messaging outputs
View examples of template skillets
terraform¶
Used in conjunction with Terraform templates to deploy devices.
Examples:
Deploy generic compute resources a public cloud
Deploy a VM-series or Panorama in the public cloud
View examples of terraform skillets
workflow¶
Run a series of skillets across various configurations or skillet types.
Examples:
Query a device for attribute names then use in a configuration skillet
Load a series of day one, network, and policy skillets based on user inputs
Perform content updates before loading configuration elements
Validation dependencies before loading configuration elements
View examples of workflow skillets
Metadata Attributes¶
Complete listing of all metadata attributes used in skillets.
Preamble Attributes¶
The preamble is the top section of the skillet providing identifying items, help text, and collection information.
name: sample_validation_skilletbuilder
label: Sample Validation Skillet
description: |
Short set of validations for skilletBuilder training tutorial with ntp check, password complexity,
URL filtering for malware, and security allow rules with profiles or groups
type: pan_validation
labels:
collection:
- Skillet Builder
- Validation
order: 10
help_link: https://skilletbuilder.readthedocs.io/en/latest/reference_examples/builder_tools.html#sample-validation-skillet
help_link_title: SkilletBuilder sample validation skillet
name¶
Globally unique identifier for the skillet referenced by skillet tools and workflows.
Note
The skillet name must not contain special characters such as ‘-‘ or ‘*’ or spaces. Variable names can be any length and can consist of uppercase and lowercase letters ( A-Z , a-z ), digits ( 0-9 ), and the underscore character ( _ ). An additional restriction is that, although a variable name can contain digits, the first character of a variable name cannot be a digit.
label¶
A short descriptive name identifying the skillet that is shown in PanHandler selection tiles.
description¶
A contextual description of the skillet presented to the user. This may include quick caveats, reminders, and skillet intent.
type¶
One of the predefined Skillet Types allowing tools to determine how to play the skillet.
labels¶
Optional key/value pairs adding complimentary parameters that may not be implemented by all tools. See Labels Attributes below for currently supported PanHandler labels.
Labels Attributes¶
Labels are key/value pairs attached to skillets. Labels are optional and allow adding additional parameters to Skillets that may not be implemented by all utilities. Labels can be used for grouping, searching, sorting, and identifying skillets beyond just a name attribute. Labels can be used to extend Skillet functionality in arbitrary ways going forward. This behavior is very much influenced by BGPv4 labels and Kubernetes labels.
PanHandler recognizes the following labels:
collection¶
The collection label is used to group like skillets. A skillet may belong to multiple collections. The collection label value is a list of collection to which the skillet belongs. Skillets with no collection label will be placed in the Unknown Collection.
labels: collection: - Example Skillets - Test Skillets - Validation Skillets
order¶
PanHandler uses the order label to sort the skillets. Skillets without an order label are sorted alphabetically by their label attribute. Skillets with a lower order tag will be display before those with a higher order tag.
labels: order: 10
help_link¶
The help_link label can be used to display a link to additional documentation about a skillet. This will be shown in the Help dialog from the ? icon in the top right hand corner of the skillet input form.
labels: help_link: https://panhandler.readthedocs.io/en/master/variables.htmlThe help_link_title will set the displayed title of the help_link in the Help dialog.
labels: help_link: https://panhandler.readthedocs.io/en/master/variables.html help_link_title: All available Variable Documentation
Variables Attributes¶
The variables section is used to define variables and web UI attributes.
variables:
- name: INTF_UNTRUST
description: internet Interface
default: ethernet1/1
type_hint: dropdown
source: interface_names
- name: INTF_TRUST
description: internal Interface
default: ethernet1/2
type_hint: dropdown
source: interface_names
- name: IP_12
description: internal interface ip address
default: 192.168.45.20/24
type_hint: text
- name: tag_color
description: tag color
default: red
type_hint: dropdown
dd_list:
- key: green
value: color2
- key: orange
value: color6
- key: red
value: color1
name¶
A name assigned to the variable.
Note
The variable name must not contain special characters such as ‘-‘ or ‘*’ or spaces. Variable names can be any length and can consist of uppercase and lowercase letters ( A-Z , a-z ), digits ( 0-9 ), and the underscore character ( _ ). An additional restriction is that, although a variable name can contain digits, the first character of a variable name cannot be a digit.
description¶
A description of the variable usage and can be displayed as part of a web form.
default¶
A default value of the variable, which is typically set to a recommended value.
type_hint¶
One of the predefined variable types and associates to web form validation. Some variable types, such as dropdown, will use additional key/value pairs or source options for user selection. See Variables for a complete list of type_hints and dynamic UI elements.
source¶
Used in lieu of static key/value pairs in type hints such as dropdown to dynamically create user selections. See source for details and examples.
toggle_hint¶
Shows a field based on a reference field value. See toggle_hint for details and examples.
Snippets Attributes¶
name¶
Name of the snippet. Specifically for workflow type skillets, name references the Preamble name of a skillet to play.
cmd¶
Command action to be performed. The default and values vary by skillet type. See cmd Options for additional details.
xpath¶
The XPath used for set, edit, and delete cmd options for panos/panorama.
element¶
The XML element used for set, edit, and delete cmd options for panos/panorama.
file¶
A skillet file to be read. This can either be a template file for template skillets, python file for python3 skillets, or an XML file for panos/panorama skillets.
path¶
The URI path for REST skillets.
operation¶
The REST operation, either POST or GET, for REST skillets.
headers¶
The headers used as part of a REST API call in REST skillets.
output_type¶
The data format for response outputs.
outputs¶
The outputs assigned to a variable. The format is defined using Capture Output options.
input_type¶
Used in python3 skillets to specify method for parsing arguments.
image¶
Docker image type, such as Alpine.
label¶
A descriptive text associated with a test in validation skillets.
severity¶
Indicates user-defined severity for a test in validation skillets.
fail_message¶
The output message when a test fails in validation skillets.
pass_message¶
The output message when a test passes in validation skillets.
test¶
A Boolean logic test that is evaluated for validation skillets.
documentation_link¶
A documentation reference associated to a test in validation skillets.
when¶
A conditional logic that only performs a test with when is True.
transform¶
A dictionary that maps the output from one sub-skillet to the input of another in workflow skillets. See the `skilletlib Workflow with Transform`_ example skillet for formatting help.
template
name - name of this snippet
file - path to the Jinja2 template to load and parse
template_title - (Optional) title to include in rendered output
when - (Optional) conditional logic for snippets execution
terraform
None - snippets are not used for terraform
See Example here: Example Terraform Skillet
workflow
name - name of this sub-skillet to play
when - (Optional) conditional logic for sub-skillet execution
transform - (Optional) mapping of another snippet’s output variable to this snippet’s input variable.
cmd Options¶
set¶
Merges element into the candidate configuration for panos/panorama skillets.
edit¶
Replaces configuration element with new element for panos/panorama skillets.
delete¶
Deletes part of the configuration for panos/panorama skillets.
get¶
Pulls information from a device for panos/panorama skillets.
move¶
Moves a configuration element for panos/panorama skillets.
parse¶
Parses an input file.
cli¶
Run an operations CLI commands such as
show system info
for panos/panorama/validation skillets.
validate¶
Run a validation test for validation skillets.
validate_xml¶
TBD; validation
noop¶
TBD; validation
custom inputs¶
In this case instead of a cmd option, the skillet includes a command line string, such as
ansible playbook command
.
rest
Snippet Attributes per Skillet Type¶
Below describes the fields for a snippet depending on the skillet type:
docker
name - name of this snippet
image - Docker image to run
cmd - the command to run inside of the Docker container
when - (Optional) conditional logic for snippets execution
panos, panorama, panorama-gpcs
name - name of this snippet
cmd - operation to perform. Default is
set
. See the cmd Options for all available options.xpath - XPath where this fragment belongs
file - path to the XML fragment to load and parse. Interchangeable with element
element - inline XML fragment to load and parse. Interchangeable with file
when - (Optional) conditional logic for snippets execution
See Example here: Example PAN-OS Skillet
pan_validation
name - name of the validation test to perform
cmd - validate, validate_xml, noop, or parse. Default is validate
test - Boolean test to perform using Jinja2 expressions
when - (Optional) conditional logic for snippets execution
See Example here: Example Validation Skillet
python3
name - name of the script to execute
file - relative path to the python script to execute
input_type - Optional type of input required for this script. Valid options are ‘cli’ or ‘env’. This will determine how user input variables will be passed into into the script. The default is cli and will pass variables as long form arguments to the script in the form of
--username=user_input
whereusername
is the name of the variable defined in the variables section anduser_input
is the value entered for that variable from the user. The other option, env, requires all defined variables to be set in the environment of the python process.when - (Optional) conditional logic for snippets execution
See Example here: Example Python Skillet
rest
name - unique name for this rest operation
path - REST URL path component
path: http://host/api/?type=keygen&user={{ username }}&password={{ password }}
operation - type of REST operation (GET, POST, DELETE, etc)
payload - path to a Jinja2 template to load and parse to be send as POSTed payload. For
x-www-form-urlencded
, this must be a json dictionaryheaders - a dict of key value pairs to add to the http headers. For example,
Content-Type: application/json
.when - (Optional) conditional logic for snippets execution
See Example here: Example REST Skillet and here: Example REST Skillet with Output Capturing
template
name - name of this snippet
file - path to the Jinja2 template to load and parse
template_title - (Optional) title to include in rendered output
when - (Optional) conditional logic for snippets execution
terraform
None - snippets are not used for terraform
See Example here: Example Terraform Skillet
workflow
name - name of this sub-skillet to play
when - (Optional) conditional logic for sub-skillet execution
transform - (Optional) mapping of another snippet’s output variable to this snippet’s input variable.
name - unique name for this rest operation
path - REST URL path component
path: http://host/api/?type=keygen&user={{ username }}&password={{ password }}
operation - type of REST operation (GET, POST, DELETE, etc)
payload - path to a Jinja2 template to load and parse to be send as POSTed payload. For
x-www-form-urlencded
, this must be a json dictionaryheaders - a dict of key value pairs to add to the http headers. For example,
Content-Type: application/json
.when - (Optional) conditional logic for snippets execution
Variables¶
Variables in a Skillet determine what a user can modify or customize before deployment. In Panhandler, these get generated into a web form that a user can fill out. Each variable can have it’s own ‘type’ determined by the ‘type_hint’ attribute in the variable declaration. This page lists all the available type hints for reference.
The Skillet repo contains a sample skillet with all inputs.
Variable Types¶
cidr¶
This type will ensure the entered value matches an IPv4 or IPv6 CIDR.
- name: ip_address
description: IP Address
default: 192.168.122.2/24
type_hint: cidr
disabled¶
This type will show the default value in an input control, but the user cannot change it. This is useful to show values but not allow then to be changed.
- name: DISABLED
description: No Bueno
default: panos-01
type_hint: disabled
dropdown¶
This type will render a select input control. This ensures the user can only select one of the options given in the dd_list.
- name: yes_no
description: Yes No
default: 'no'
type_hint: dropdown
dd_list:
- key: 'Yes I do'
value: 'yes'
- key: 'No I dont'
value: 'no'
Note
The default parameter should match the value and not the key. The key is what will be shown to the user and the value is what will be used as the value of the variable identified by name.
Warning
Some values such as yes, no, true, false, on, off, etc are treated differently in YAML. To ensure these values are not converted to a boolean type, ensure to put single quotes ‘ around both the key and the value as in the example above. Refer to the YAML specification for more details: https://yaml.org/type/bool.html
email¶
This type will ensure the entered value matches an email pattern.
- name: email
description: Email
default: support@noway.com
type_hint: email
help_text: Enter your email address here to receive lots of spam
file¶
This type will upload a file to a temporary directory and set the variable value to the full path to the file. This is useful for python Skillets to take the file path as an input and open and handle the file contents itself.
- name: uploaded_file_path
description: Upload a File
default:
type_hint: file
float¶
This type will ensure the entered value is a float. You may optionally supply the min and max attributes to ensure the entered value do not exceed or fall below those values.
- name: price_per_mbps
description: Price Per Mbps
default: 1.50
type_hint: float
attributes:
min: 1.00
max: 500.00
fqdn_or_ip¶
This type will ensure the entered value matches an IPv4, IPv6, or a valid hostname pattern. This is the most flexible option for hostname, FQDNs, ip addresses or CIDRs.
- name: host
description: Target Host
default: 0.pool.ntp.org
type_hint: fqdn_or_ip
ip_address¶
This type will ensure the entered value matches an IPv4 or IPv6 pattern without a subnet mask.
- name: ip_address
description: IP Address
default: 0.0.0.0
type_hint: ip_address
json¶
This type renders a TextArea input control and ensures the input is properly formatted JSON data
- name: json_string
description: JSON Input
default: |
{
"key_test": "value_test",
"key2_test": "value2_test",
}
type_hint: json
list¶
This type will allow the user to input multiple entries. The values of the multiple entries will be converted to an appropriate type for the Skillet type being used. For python, the entries will be converted to a comma separated list. For Terraform, the values will be converted to a terraform appropriate string representation.
- name: list_input
description: IP Subnets
default: 10.10.10.1/24
type_hint: list
number¶
This type will ensure the entered value is an integer. You may optionally supply the min and max attributes to ensure the entered value do not exceed or fall below those values.
- name: vlan_id
description: VLAN ID
default: 1001
type_hint: number
attributes:
min: 1000
max: 2000
password¶
This type will mask user input by rendering a password type input box.
- name: user_password
description: Firewall Password
default:
type_hint: password
radio¶
This type allows the user to select one option out of the rad_list.
- name: radio_box_example
description: radios
default: maybe
type_hint: radio
rad_list:
- key: 'Yes'
value: 'yes'
- key: 'No'
value: 'no'
- key: 'Maybe'
value: 'maybe'
text¶
Default input type for user input. Optional allow_special_characters if false will ensure only letters, digits, underscore, hyphens, and spaces are allowed in the input. Set to True to allow all special characters. Default is to allow special characters. Optional attributes allows forcing a minimum and/or maximum length of the entered value.
- name: FW_NAME
description: Firewall hostname
default: panos-01
type_hint: text
help_text: Hostname for this firewall.
allow_special_characters: false
attributes:
min: 6
max: 256
text_area¶
This type renders a TextArea input control. This allows the user to enter multiple lines of input. The optional attributes attribute allows you to customize the size of the text area control.
- name: text_area
description: Multi-Line Input
default: |
This is some very long input with lots of
newlines and white space
and stuff. The optional attributes key can also be specified
to control now the text_area is rendered in panhandler and other cnc apps.
type_hint: text_area
attributes:
rows: 5
cols: 10
url¶
This type will ensure the entered value matches a valid URL scheme.
- name: clone_url
description: Git Repo Clone URL
default: https://github.com/PaloAltoNetworks/Skillets.git
type_hint: url
Dynamic UI Elements¶
In some cases, it may be desirable for the UI to be more dynamic. Each variable can include ‘hints’ about how the UI should behave, but these, of course, are not guaranteed to be implemented in all tooling. Panhandler will produce dynamic UI elements in the following cases:
source¶
The optional source attribute on dropdown, radio, and checkbox type_hints will use the value of the ‘source’ attribute as a variable. If this variable is found in the context and it is a list, it’s value will be used to populate the form control. If the variable is not found, the form control reverts to a standard ‘text’ input as a fallback.
- name: selected_interface description: Interface default: not-saved type_hint: dropdown source: interface_namesIf the ‘type_hint’ is ‘text’ and the ‘source’ variable is a list, then multiple text input controls will be shown to the user, one for each item in the list. The resulting variable captured after the form is POSTed will be a ‘dict’ with a key for each item in the list, and it’s value from the user. This is useful to capture things like an ip address for each interface in a list.
- name: interface_ips description: Interface IP Address For default: 10.10.10.10 type_hint: text source: interface_namesIn this example, a text input control will be generated for each of the items found in the ‘interface_names’ list. Assume the ‘interface_names’ variable contained the following:
"interface_names": [ "ethernet1/1", "ethernet1/2", "ethernet1/3", "ethernet1/4", ]The resulting UI form will include 4 Text inputs. The item in the list will be appended to the description and used as the text input label. After the user fills in the information in all 4 text inputs, the interface_ips variable in the jinja context will have the following structure:
"interface_ips": { "ethernet1/1": "10.10.10.11", "ethernet1/2": "10.10.10.12", "ethernet1/3": "10.10.10.13", "ethernet1/4": "10.10.10.14", }Panorama Variables
This model is also useful when working with Panorama template variables. A list of Panorama variable names can be the source and the user-entered values captured as a dict. The snippet below shows the use of a Jinja For loop iterating over the dict ‘template_variables’ as part of device onboarding.
<entry name="{{ serial_number }}"> <variable> {%- for var_name, var_value in template_variables.items() %} <entry name="{{ var_name }}"> <type> <ip-netmask>{{ var_value }}</ip-netmask> </type> </entry> {% endfor %} </variable> </entry>
toggle_hint¶
The optional ‘toggle_hint’ attribute will show a field only when the ‘source’ variable’s value matches the configured ‘value’. If the ‘source’ is not found, or it’s current value does not match ‘value’, this form control will be hidden. This is especially useful when paired with a ‘dropdown’ select control.
- name: bgp_asn
description: Only Required when BGP is enabled
default: 64000
type_hint: text
toggle_hint:
source: bgp_type
value: enable
Panhandler Generated UI¶
Because Skillets are essentially tooling agnostic, it’s up to the tool to implement the UI presented to the user. Some tools may prefer a different approach, or may not even need a UI at all. For example, in a CI/CD pipeline, the value of the variables may be obtained via the OS environment. A script may use command line arguments, etc.
Panhandler generates a fully customized UI for each Skillet that is configured via the types of ‘type_hint’ supplied with each variable. By default, this is a static web form with a single input form control for each variable.
Jinja and Skillets¶
Jinja is a templating language for Python and used within the skillet framework to:
Content objects are collected from the skillet and passed through the rendering engine.
The video tutorial covers Jinja variables, if conditionals, and for loops.
Jinja Variable¶
Variables are used in the Snippets sections of the .meta-cnc.yaml file and externally referenced text files to:
provide simple substitutions for values in an XML or set command configuration file
define variable-based XPaths such as Panorama templates and device-groups
create contextual output pass/fail messages in validation skillets
Variables are added using a {{ variable }}
syntax.
The example below uses three variables: tag_name, tag_color, and tag_description
snippets:
- name: object_tag
xpath: /config/devices/entry[@name="localhost.localdomain"]/vsys/entry[@name="vsys1"]/tag
element: |-
<entry name="{{ tag_name }}">
<color>{{ tag_color }}</color>
<comments>{{ tag_description }}</comments>
</entry>
The skillet player captures the variable values with output as a rendered XML element.
entry name="block_rule">
<color>color1</color>
<comments>block rules based on EDL destinations</comments>
</entry>
Ensuring All Variables Are Defined¶
When working with a large amount of configuration templates, it’s easy to miss a variable definition. Use this one-liner to find them all.
Change directories into a skillet directory and run this to find all configured variables:
grep -r '{{' . | cut -d'{' -f3 | awk '{ print $1 }' | sort -u
Or, if you have perl available, the following may also catch any configuration commands that may have more than one variable defined:
grep -r '{{' . | perl -pne 'chomp(); s/.*?{{ (.*?) }}/$1\n/g;' | sort -u
Jinja If Conditional¶
If conditionals can be used in a skillet to decide if part of an element is rendered based on a variable value.
In this example a dropdown is used in the metadata file to input dhcp or static IP addressing.
variables:
- name: MGMT_TYPE
description: firewall management IP type
default: dhcp-client
type_hint: dropdown
dd_list:
- key: dhcp-client
value: dhcp-client
- key: static
value: static
- name: MGMT_IP
description: NGFW management IP
default: 192.0.2.6
type_hint: ip_address
- name: MGMT_MASK
description: NGFW management netmask
type_hint: ip_address
default: 255.255.255.0
- name: MGMT_DG
description: NGFW management default gateway
default: 192.0.2.7
type_hint: ip_address
Below is the XML element with the if conditional embedded. The conditionals are placed within {% if content %}
using built-in Jinja expressions.
Choosing MGMT_TYPE = static will include the IP address, netmask, and gateway elements while ignoring the DHCP configuration. If the selection is DHCP the inverse is true with only the DHCP settings rendered.
<type>
{%- if MGMT_TYPE == "static" %}
<static/>
{% elif MGMT_TYPE == "dhcp-client" %}
<dhcp-client>
<send-hostname>yes</send-hostname>
<send-client-id>no</send-client-id>
<accept-dhcp-hostname>no</accept-dhcp-hostname>
<accept-dhcp-domain>no</accept-dhcp-domain>
</dhcp-client>
{% else %}
<dhcp-client>
<send-hostname>yes</send-hostname>
<send-client-id>no</send-client-id>
<accept-dhcp-hostname>no</accept-dhcp-hostname>
<accept-dhcp-domain>no</accept-dhcp-domain>
</dhcp-client>
{% endif %}
</type>
{%- if MGMT_TYPE == "static" %}
<ip-address>{{ MGMT_IP }}</ip-address>
<netmask>{{ MGMT_MASK }}</netmask>
<default-gateway>{{ MGMT_DG }}</default-gateway>
{% endif %}
Here is the output if static is selected:
<type>
<static/>
</type>
<ip-address>192.0.2.6</ip-address>
<netmask>255.255.255.0</netmask>
<default-gateway>192.0.2.7</default-gateway>
And the output if dhcp-client is selected:
<type>
<dhcp-client>
<send-hostname>yes</send-hostname>
<send-client-id>no</send-client-id>
<accept-dhcp-hostname>no</accept-dhcp-hostname>
<accept-dhcp-domain>no</accept-dhcp-domain>
</dhcp-client>
</type>
Jinja For Loop¶
For loops can be used in a skillet to capture a list of information and iterate over the list as multiple entries.
In this example a list of serial numbers are onboarded to Panorama.
variables:
- name: serial_number
description: Device serial number
default: 12345
type_hint: list
help_text: basic onboarding to panorama; click + to add additional devices
Below is the XML element with the for loop embedded. The conditionals are placed within {% for content %}
using Jinja built-in for loop logic.
{% for item in serial_number %}
<entry name="{{ item }}"/>
{% endfor %}
The rendered output element is:
<entry name="1234567890"/>
<entry name="1234567891"/>
<entry name="1234567892"/>
adding in each serial number in the variable list.
Jinja Filter¶
Jinja filters have a few roles in skillets:
reformat data
boolean logic for validation tests
output passwords as hashes
These filters can be built-in filters or custom skillet filters.
Filters are used by including `` | filter `` after a variable:
Variable | Filter example |
Filter action |
---|---|
var | length |
True if a list var has values |
var | length == 0 |
True if a list var is empty |
var | md5_hash |
convert a password to a phash |
var | element_value(‘config_value’) == ‘yes’ |
True if the XML config_value = yes |
var | tag_present(‘config_tag’) |
True if the XML tag exists |
var | replace (“old”, “new”) |
replace a string or substring with a new value |
var | int |
convert a string to an integer |
Jinja Whitespace Control¶
Care must usually be taken to ensure no extra whitespace creeps into your templates due to Jinja looping constructs or control characters. For example, consider the following fragment:
<dns-servers>
{% for member in CLIENT_DNS_SUFFIX %}
<member>{{ member }}</member>
{% endfor %}
</dns-servers>
This fragment will result in blank lines being inserted where the for and endfor control tags are placed. To ensure this does not happen and to prevent any unintentioal whitespace, you can use Jinja whitespace control like so:
<dns-servers>
{%- for member in CLIENT_DNS_SUFFIX %}
<member>{{ member }}</member>
{%- endfor %}
</dns-servers>
Note
Note the ‘-‘ after the leading ‘{%’. This instructs jinja to remove these blank lines in the resulting parsed output template.
Custom Jinja Filters¶
Custom filters are used to simplify validation skillets by using a small set of filter options to check the most common configuration components contained in tags, attributes, and element text values.
Review the XML Basics documentation for the XML terminology used in the custom filters.
Additional examples can be found in the skilletlib examples directory
Capturing XML Objects¶
In order to properly validate a config it is often necessary to convert the XML structure to an object, which can then be used in a Jinja expression to perform basic logic and validation. The captured object is associated to an XPath plus its corresponding XML element and assigned a variable name used in the custom filter.
Each custom filter example below shows its respective captured object for use in the filter.
When building skillets, the Builder needs to:
know the XPath for each object to capture
determine what part of the XML element will be referenced: attribute, tag, element text value
which custom filter to select based on the XML element reference
what conditions have to be met: a specific or range of values, item present or absent, etc.
Checking Attributes¶
Attribute filters are most commonly used to check object names although other attributes can exist within the XML configuration.
attribute_present(tag name, attribute name, attribute value)
attribute_absent(tag name, attribute name, attribute value)
The attribute being checked in this example is the external-list entry name. Therefore the input values for the attribute filters are:
tag name: <entry>
attribute name: ‘name’
the attribute value of interest
A sample XML element found at XPath /config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']/external-list
is used as reference for the attribute custom filters examples. The element will be a captured object variable called
external-list
.
<external-list>
<entry name="tutorial_edl">
<type>
<ip>
<recurring>
<five-minute/>
</recurring>
<description/>
<url>http://tutorial.com</url>
</ip>
</type>
</entry>
<entry name="my_edl">
<type>
<ip>
<recurring>
<five-minute/>
</recurring>
<description/>
<url>http://my_url.com</url>
</ip>
</type>
</entry>
</external-list>
attribute_present¶
Checks if an attribute value exists and returns True if the attribute value is found. This filter is used to ensure a named object or policy exists in the configuration. This item should be present as part of a best practice validation or other config skillets may have dependencies on this item.
external-list | attribute_present('entry', 'name', 'my_edl') external-list | attribute_absent('entry', 'name', 'new_edl')The first filter will return True since my_edl is in the external-list object. The second filter will return False since new_edl is not in the external-list object.
attribute_absent¶
Checks if an attribute value exists and returns True if the attribute value is not found. This filter is used to ensure a named object or policy does not already exist in the configuration. If the item exists it may cause config merge conflicts or override an existing configuration.
external-list | attribute_absent('entry', 'name', 'my_edl') external-list | attribute_absent('entry', 'name', 'new_edl')The first filter will return False since my_edl is in the external-list object. The second filter will return True since new_edl is not in the external-list object.
Checking an Element Value¶
Element value filters are most commonly used to check specific text values in the XML configuration.
element_value('tag name') [expression] value
Any valid jinja expression can be used to evaluate the text value.
A sample XML element found at XPath /config/devices/entry[@name='localhost.localdomain']/deviceconfig/system
will be used as reference for the element value custom filter example. The element will be a captured object variable called
device_system
.
<update-schedule>
<anti-virus>
<recurring>
<hourly>
<at>4</at>
<action>download-and-install</action>
</hourly>
</recurring>
</anti-virus>
<wildfire>
<recurring>
<every-min>
<action>download-and-install</action>
</every-min>
</recurring>
</wildfire>
</update-schedule>
<snmp-setting>
<access-setting>
<version>
<v3/>
</version>
</access-setting>
</snmp-setting>
<ntp-servers>
<primary-ntp-server>
<ntp-server-address>0.pool.ntp.org</ntp-server-address>
</primary-ntp-server>
<secondary-ntp-server>
<ntp-server-address>1.pool.ntp.org</ntp-server-address>
</secondary-ntp-server>
</ntp-servers>
<login-banner>You have accessed a protected system.
Log off immediately if you are not an authorized user.
</login-banner>
<timezone>EST</timezone>
element_value¶
Checks an element_value expression and returns True if the expression is true. This filter is used to check a specific value or range based on best practices or expected configuration settings. Various checks such as ‘==’, ‘!=’, ‘>=’, and ‘<=’ can be used in the filter.
device_system | element_value('update-schedule.wildfire.recurring.every-min.action') == 'download-and-install' device_system | element_value('timezone') == 'UTC'The first filter uses the
dot notation
to step down the tree to the wildfire dynamic update action. This allows a single captured object to be used for multiple tests instead of an explicit capture object for each test using a granular XPath. The filter will return True since the action for Wildfire updates is set to ‘download-and-install’.The second filter will return False since the XML configuration for timezone is ‘EST’ and not ‘UTC’.
Checking Tags¶
The tag filters are most commonly used to check specific tags that are used in the data structure as configuration values.
tag_present('tag name')
tag_absent('tag name')
The example below references the same device_system
captured object used in the element_value example.
tag_present¶
Checks for a tag name and returns True if the tag is found. This filter is used to check for a specific tag in cases where configuration values are tags instead of text values. In the device_system example the recurring interval for Wildfire updates is a tag shown as
<every-min>
device_system | tag_present('update-schedule.wildfire.recurring.every-min') device_system | tag_present('update-schedule.wildfire.recurring.every-hour')The filters use the
dot notation
to step down the tree to the wildfire recurring interval. This allows a single captured object to be used for multiple tests instead of an explicit capture object for each test using a granular XPath.The first filter will return True since the Wildfire update interval is set to ‘every-min’. The second filter will return False since every-hour is not found.
Other examples using tag_present from the same device_system capture object:
device_system | tag_present('snmp-setting.access-setting.version.v3') --> check if SNMP v3 configured device_system | tag_present('ntp-servers.primary-ntp-server') --> check if an NTP server is configured
tag_absent¶
Checks for a tag name and returns False if the tag is found. This filter is used to check for tag-based configuration components that should NOT exist in the configuration. In the device_system example the recurring interval for Wildfire updates is a tag shown as
<every-min>
device_system | tag_absent('update-schedule.wildfire.recurring.every-min') device_system | tag_absent('update-schedule.wildfire.recurring.every-hour')The filters use the
dot notation
to step down the tree to the wildfire recurring interval. This allows a single captured object to be used for multiple tests instead of an explicit capture object for each test using a granular XPath.The first filter will return False since the Wildfire update interval is set to ‘every-min’. The second filter will return True since every-hour is not found.
Checking a Set of Element Values¶
In some cases multiple values are contained with a portion of the configuration. These are often referenced in the configuration file with <member> tags. Examples of multiple entries include:
zones, addresses, users, or tags assigned to a security policy
URL categories assigned to block or alert actions
interfaces assigned to a zone or virtual-router
To check multiple element values, the element_value_contents custom filter can search across all members to find a specific value.
element_value_contains¶
The inputs to the filter are the tag name and the search value.
element_value_contains('tag name', 'search value')
This example checks a security rule to see if a specific destination address using an external-list is found. The XPath for the Outbound Block Rule is /config/devices/entry[@name=’localhost.localdomain’]/vsys/entry[@name=’vsys1’]/rulebase/security/rules/entry[@name=’Outbound Block Rule’]
Below is an abbreviated XML element showing the <destination> content of interest.
<entry name="Outbound Block Rule">
<to>
<member>any</member>
</to>
<from>
<member>any</member>
</from>
<destination>
<member>panw-highrisk-ip-list</member>
<member>panw-known-ip-list</member>
<member>panw-bulletproof-ip-list</member>
</destination>
<action>deny</action>
<log-setting>default</log-setting>
<tag>
<member>Outbound</member>
</tag>
</entry>
The custom filter looks for the inclusion of the panw-bulletproof-ip-list EDL as a destination address.
security_rule_outbound_edl | element_value_contains('destination.member', 'panw-bulletproof-ip-list')
Since the member value is found a True result is returned.
Referencing the same example, other element_value_contains checks could be used for <to> or <from> zones and <tag> members.
XML and Skillets¶
A basic understanding of eXtensible Markup Language (XML) structure and terminology is required for PAN-OS and Panorama skillets. These devices use XML as the format for the configuration file and operational command responses.
A more extensive knowledge of XML is required for validations skillets in order to capture output and perform tests against the XML configurations and operational commands.
You can explore the XML standard but it isn’t required for skillet work. Instead we’ll focus on the XML details of the device configuration files.
XML Basics¶
The basics covers the essentials of XML terminology and structure.
XML Format¶
The format of XML is related to HTML and uses a common set of terms:
tags: start and end tags formatted as <tag> and </tag>
attributes and values: placed inside the tag as attribute=”value”
element or ‘text’ values: text or numbers as values between associated tags
This example shows how these items are used in HTML. HTML has pre-defined tags for elements such as headers and paragraphs since HTML is focused on page display. The attributes are used for formatting, href links, and other style components. The text values are items displayed on screen.
You can see that the XML format is similar. There are <tags>, some with attributes=”value”, and text between tags. Unlike HTML, XML uses custom tags which is what makes it extensible.
XML Structure¶
At this stage we’ll end the comparison to HTML. Although the format is the same, the structure is very different. Whereas HTML is used to describe presentation, XML is used to describe data.
Folder-based Hierarchy¶
Therefore the best comparison used for XML structure is a folder-based data structure with each <tag> as a folder. The example shows a representation of the XML file as folders.
The ‘config’ tag is the top level folder. The second level folders/tags include mgt-config, devices, and shared. Opening mgt-config shows the next level down, users and password-complexity. This model of nested tags creates the structure of the configuration data file. The job of skillets is to edit or read this data file structure.
Paths and XPaths¶
Using the same folder model I would reference the users folder path as /config/mgt-config/users
.
The path is just a series of folder names separated by ‘/’ showing where I am in the data structure.
XML uses the same concept renaming path to XPath
.
The XPath equivalent is /config/mgt-config/users
identical to the folder-based example above.
So the XPath is just a chain of tags separated by ‘/’ stating where a piece of data is located in the file.
In the prior XML example look for the sequence of tags <config>, <mgt-config>, <users> to see the XPath hierarchy
in the raw XML configuration file.
Attributes and Values in the XPath
When multiple elements exist in a section of the file with the same tag, the attribute and value are added into each tag
to create a unique XPath branch. This is shown in the XPath with [@attribute="value"]
appended after the tag.
A couple of examples are below.
/config/devices/entry[@name="localhost.localdomain"]/vsys/entry[@name="vsys1"]/tag
This XPath includes two attributes for the localhost name and the vsys name. Using the vsys example, this configuration is specific to vsys1 while other vsys names could be referenced where needed.
/config/devices/entry[@name="localhost.localdomain"]/vsys/entry[@name="vsys1"]/tag/entry[@name="Inbound"]
/config/devices/entry[@name="localhost.localdomain"]/vsys/entry[@name="vsys1"]/tag/entry[@name="Outbound"]
In this example each XPath refers to a specific tag object entry based on its name, Inbound or Outbound. The attribute
and values are required since the XML tag entry
is the same for each tag configured.
Tools to Find the XPath¶
Knowing the XPath is key for most of the configuration and validation skillets. Here are a few ways to find the XPath for a specific configuration element.
The video tutorial shows examples of capturing the XPath and associated XML elements.
For each documentation example we’ll use the same XPath for a NGFW tag object. Each example results in the same XPath.
Web UI Debug¶
The Web UI Debug is a great starting place if you aren’t sure where to begin and are familar with Web UI configuration.
Log into the device and then in another browser tab navigate to https://$NGFW_IP/debug
where $NGFW_IP is the
device IP address.
Check Debug
and Clear debug
to get started.
In the configuration UI navigate to Objects > Tags and add a new tag. No need to commit.
Back in the debug tab click Refresh
to view the debug output. You’ll see lots of text scroll across the screen.
To find the configuration change, search for cmd="edit"
or cmd="set"
. In our case set is required since a new
tag. If you make changes to an existing item then use edit.
Just to the right of the set or edit will be obj=
with the XPath. The XPath in this example is:
/config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']/tag/entry[@name='demo_tag']
and ignoring the entry for the tag created the XPath for all tags would be:
/config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']/tag
Web UI XML API Explorer¶
The Web UI XML API Explorer is a web version of the CLI interface designed to view API information.
Log into the device and then in another browser tab navigate to https://$NGFW_IP/api
where $NGFW_IP is the
device IP address.
Click through to the tag configuration:
Configuration Commands > devices > entry[@name='localhost.localdomain'] > vsys > entry[@name='vsys1'] > tag
As you click through you’ll notice the entry in the XPath window shows your current XML tree location.
Clicking tag
at the end gives the XPath as:
/config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']/tag
CLI Debug¶
If you are familiar with the device CLI commands or are using commands that aren’t found in Web UI this is a preferred option.
Simply enter debug cli on
, configure
, and show tag
.
The highlighted text just above the tag configuration shows the XPath as:
/config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']/tag
To view the tag XML element, enter set cli config-output xml
in operation mode and enter show tag
in configure mode.
This switches the config view from JSON to XML.
Skillet Generator¶
This option uses the generator to output one or more XPaths based on configuration changes between two files.
To use the Skillet Generator see the Generate a Skillet documentation.
Save a baseline configuration, add a tag, and export the candidate configuration. Use these two files in the generator.
The output snippet includes the XPath:
/config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']/tag
Parsing XML¶
The primary requirement for creating configuration skillets is to know the XPath and associated XML element. For other types of skillets such as validations and REST, more extensive XML skills are needed to parse XML data.
Parsing XML uses the XPath and various syntax options to generate output including:
XML elements
a specific value
lists of values
This output is used in various ways in tests, pulldown menu options, or input to other skillets.
The Configuration Explorer Tool will be used to view XML parsing outputs.
Parsing Syntax Basics¶
Parsing the XML file starts with the base XPath which is appended based on the data to be output. The various options will align to the type of output: element, value, list.
Common items used for parsing configurations and outputs include:
Use in XPath Query |
How Impacts the Query and Output |
---|---|
append XPath with attribute |
list of names for the last XPath tag (eg. <entry>) |
append XPath with text() |
text value of the last XPath tag |
use tag_name[text()=’text_value’] |
filter results where tag_name has a specific text_value |
use |
wildcard to look across XML branches |
Use |
reference items one level up in the tree for each /../ |
The following sections show examples using the query syntax options above.
Output an XML Element¶
The simplest parsing simply returns an XML Element.
Using the XPath from the examples above
/config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']/tag
the output from the Explorer shows the tag XML elements along with a json snippet.
This entry has 3 tags: block_list, tag name, and demo_tag.
Output a List based on Attribute Name¶
This parsing example will return a list of tag names by appending the XPath with /entry/@name
where entry is
the tag of interest and name is the attribute.
The new XPath to query is
/config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']/tag/entry/@name
and the output is a list of items: the tag names.
Output a List Filtered on a Text Value¶
Building on the example, filters can be used to limit the output. In this example we’ll filter the output looking for tags with color = color1.
The new XPath to query is
/config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']/tag//color[text()='color1']/../@name
and the output is now a list of items, the tag names with color1.
Let’s break this down referencing the XML element output in Output an XML Element
The
//
before color is used to skip levels of the XPath, specifically where the tree branches and multiple entries exist. This allows us to search all of the <tag> entries.Including the
color[text()='color1']
filter captures only the elements with text value = color1. At this point we have captured all of the color1 <tag> elements but the goal is to get only the tag object names.Using
/../
we come back up the tree one level from <color> to the <entry> level of the XPath. Each/../
included in the XPath brings us up one level where/../../
would be up two levels. We only need to return one level to <entry>.Appending
@name
gives us the attribute name values found in our captured elements.
The output is the filtered list based on a color value = color1.
Note
Using the Explorer you can try variations of the above XPath syntax without the /../, @name, or filter. You can also modify the filter color to return different values.
XPath Query Tips¶
Use the Explorer and start with a known XPath and zoom into specific details
Verify what’s contained in the configuration file you are querying
If using the double dot
/../../
option make sure you properly count the number of levels required
Capture Output¶
Various models for capturing output to variables used in:
validation tests
dynamic menu options
text render outputs
capture_list¶
Use to capture a list of values. This example creates a list of all URL-filtering profile names and stores them in the varilable ‘url_filtering_profiles’.
# get list of all url profiles for debug example - name: url_filtering_profiles capture_list: |- /config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']/profiles/url-filtering/entry/@nameUsing a sample IronSkillet configuration, the output list is
url_filtering_profiles = [ "Outbound-URL", "Alert-Only-URL", "Exception-URL" ]
capture_object¶
Use to capture an XML element as a dict object. This example creates the dict using all password-complexity configuration elements and stores it in a dict variable called ‘password_complexity’.
- name: password_complexity capture_object: /config/mgt-config/password-complexityUsing a sample IronSkillet configuration, the output dict in json format is
password_complexity = { "password-complexity": { "enabled": "yes", "minimum-length": "12", "minimum-uppercase-letters": "1", "minimum-lowercase-letters": "1", "minimum-numeric-letters": "1", "minimum-special-characters": "1", "block-username-inclusion": "yes", "password-history-count": "24", "new-password-differs-by-characters": "3" } }
capture_value¶
Use to capture a single value and store as a variable. This example captures the value of the password-complexity enabled setting and stores it in a variable called ‘password_complexity_enabled’.
- name: password_complexity_enabled capture_value: /config/mgt-config/password-complexity/enabled/text()Using a sample IronSkillet configuration, the captured value is
password_complexity_enabled = "yes"
capture_pattern¶
TODO: define and determine if still used
Skillets and GitHub¶
The Skillet Framework uses GitHub as the primary option for storing skillets.
Click below to jump to a specific section:
Create a New GitHub Repository¶
Login to GitHub and select New to add a new repository.
README.md
file and an MIT license are recommended. You can also add a .gitignore
file, primarily to ignore pushing any EDI directories such as .idea/ used by PyCharm.Add SSH Keys from PanHandler into GitHub¶
Import a Repository into PanHandler¶
Create a Skillet Directory¶
Prerequisites for creating a skillet directory:
A new repository created on GitHub
Text editor/IDE of choice (PyCharm, Sublime, etc.)
From the steps above, make sure that you’ve cloned the link for the repo you just created. In a terminal/bash shell enter the following:
> git clone {GitHub repository link}This will add a directory to your local machine with the contents of the repository. Open this directory in your text editor/IDE. If you don’t already have a
README.md
file, you can add one now. Follow the ‘Configuration Tutorial’ to learn what to add in theREADME.md
file.Create a sub-directory that will contain the skillet content. Name the sub-directory something relevant to the skillet that will be created here.
Add a file with the name
.skillet.yaml
inside the sub-directory and anotherREADME.md
.Leave these files blank for now; they will be populated later on in the tutorial.
Use Submodules¶
A submodule is a reference within a host Github repository that points to a specific commit in an external repository. Submodules are used to include external content in a repository in a manner that can be easy updates and referenced. In terms of skillets, the Playlist Include skillet framework uses submodules to reference
To initiate a submodule within a host repository, use the command git submodule add <submodule_clone_link>
. This is
similar to cloning a repository to a host machine. The contents of the submodule repository will be ‘copied’ to the
working tree of the host repository and will be viewable if the host repository is cloned. It is recommended to navigate
to a folder within the host repository before initiating a submodule to keep your working tree clean. On GitHub, the
submodule will appear similarly to the ones below.
When a submodule is added to a host repository for the first time, a new .gitmodules
file will be created automatically.
This file contains information about the connection between the submodule and host repository. Adding more than one
submodule will create additional entries in the .gitmodules
file.
An example of an entry in the .gitmodules
file is:
[submodule "submodules/ironskillet-components"]
path = submodules/ironskillet-components
url = https://gitlab.com/panw-gse/as/ironskillet-components.git
When cloning a repository with a submodule, existing submodules will need to be initiated and updated before use. To do this, run the following commands:
clone the repository
open the repository
run
git submodule init
run
git submodule update
Submodules are tied to a specific commit when initiated, so they will need to be updated to pull the newest
content from the submodule repository as needed. This can be done using the git submodule update --remote --merge
command. This will update all submodules added within a host repository to the latest commit.
Skillet Builder Tools¶
The Skillet Builder tools repo contains a suite of tools to help create and test skillets.
Import to panHandler as part of the Skillet Builder sandbox. The skillets are
part of the Skillet Builder
collection.
Generate a Skillet¶
Used to generate an XML configuration skillet for PAN-OS or Panorama. The generator creates an output of XPath and XML element snippets by analyzing the difference between two XML configuration files.

Generate a Skillet steps:
Choose online or offline mode to obtain the ‘before and after’ configurations
Enter the yaml file preambles values
Copy the rendered output to the skillet .meta-cnc.yaml file
When running the generator choose between offline (From uploaded Configs) and online (From Running NGFW) mode.

Generator Offline Mode¶
Note
Export the configuration files from the NGFW or Panorama before running the generator.
Generator Online Mode¶
Note
The skillet attempts to ensure correct snippet ordering. In some cases the snippets must be manually reordered based on load order dependencies.
Skeleton YAML file attributes¶
Skillet ID: unique name for the skillet
Skillet Label: short text label used for skillet selection
Skillet description: descriptive text outlining the skillet usage
Collection Name: contextual name to group skillets
Skillet type: type of skillet (eg. panos, panorama, pan_validation)
Copy the Rendered Output to .meta-cnc.yaml¶
The output is a complete skillet metadata file. Copy the text and paste into the .meta-cnc.yaml file for the respective skillet. The .meta-cnc.yaml file can be further edited adding variables and pasted into the Skillet Test Tool for local testing without the requirement to push to Github.
The configuration tutorial skillet shows the output of the skillet generator used in the .meta-cnc.yaml file. This is the difference between an existing configuration file as base and a modified configuration file including the tag, external-list, and security policy configuration elements. After the generation, the skillet file was edited to include the variable components.
Preview XML Changes¶
Analyzes the difference between two XML files and outputs the changes in red.
When running the previewer choose between offline (From uploaded Configs) and online (From Running NGFW) mode.

XML Preview Offline Mode¶
Note
Export the configuration files from the NGFW or Panorama before running the previewer.
XML Preview Online Mode¶
View the Changes¶
After the skillet plays the output to screen includes a list of modified XPaths and the full configuration file with changes highlighted with red text.
![]()
The XPaths are active links and will jump to its respective section of the configuration file.
![]()
The red text associates to the tag and external-list XPath configuration elements.
The preview can be useful to see the configuration surrounding outputs from the skillet generator to assist with any manual skillet tuning.
Generate Set CLI Commands¶
In some cases it is preferred to use set commands instead of XML API configuration. This skillet finds the difference between two configuration files and outputs the associated set commands.
When running the generator choose between offline (From uploaded Configs) and online (From Running NGFW) mode.

Generate Set Commands Offline Mode¶
Note
Export the configuration files from the NGFW or Panorama before running the generator.
Generate Set Commands Online Mode¶
View the Rendered Output¶
Note
The skillet attempts to ensure correct set command ordering. In some cases the commands must be manually reordered based on load order dependencies.
Skillet Test Tool¶
The test tool is used to play skillets without the need to upload to Github and update the repo in panHandler. Debug outputs can be used for enhanced skillet testing.
When running the test tool choose between Offline and Online modes. Also select Debug mode if required.
Skillet Test Offline Mode¶
Note
Export the configuration files from the NGFW or Panorama before running the test tool.
Skillet Test Online Mode¶
Debug Mode¶
Skillet Content¶
Note
In panHandler this content is cached and will appear each time the Test Tool skillet is used. This allows for minor editing in the tool to quickly test skillets. However if extensive edits are required, edits should be done in the skillet editor to ensure YAML syntax and alignment is correct.
Test Tool Output¶
More detailed outputs and using the test tool is covered in the details for building skillets.
Configuration Explorer Tool¶
The Configuration Explorer Tool is used to display XML elements and values based on XML parsing syntax.
Used to discover capture outputs in validation skillets
assist with manual exploration of XPath and XML element associations

When running the explorer tool choose between Offline and Online modes. Also select Debug mode if required.
Config Explorer Offline Mode¶
Note
Export the configuration files from the NGFW or Panorama before running the test tool.
Config Explorer Online Mode¶
XPATH Query¶
Example XPath queries and syntax details are covered in the Parsing Syntax Basics documentation.
Configuration Explorer Output¶
Output details include:
the XPath queried
XML results as an XML element, value, or list of values
JSON version of the XML results
Sample Configuration Skillet¶
This skillet provides a reference configuration skillet used in the tutorial content.
Configuration includes:
View the details of the configuration skillet
Sample Validation Skillet¶
This skillet provides a reference validation skillet used in the tutorial content.
Validation includes:
View the details of the validation skillet
Skillet YAML File Template¶
This skillet uses a simple text render to generate a starter .meta-cnc.yaml formatted output.
Skeleton file inputs include:
Building and Testing with PanHandler¶
Since panHandler supports all skillet types and supports the SkilletBuilder tools, it is recommended for skillet design, build, and test.
For first time panHandler users, reference the panHandler Quickstart Guide in the Live Skillet District.
Loading the Master or Develop Versions¶
PanHandler runs in a Docker container, the master build tagged as ‘latest’.
There is also a develop branch with new features and updates. Although not the recommended release, some users may want to work with develop and explore new features. Some skillets being developed may also be dependent on newer features.
Checking your Current Version¶
You can check your panHandler version on the Welcome page. The bottom center will show the version. You will either see
a version number or DEV
if running a develop version.
Under the version is a notification message showing if you have the most recent version of PanHandler.
Updating or Running the Dev Version¶
This script will install or update to the latest ‘dev’ image for Panhandler. This is recommended for developers or power-users who understand this code may be unstable and not all features may work all the time.
curl -s -k -L http://bit.ly/34kXVEn | bash
Updating or Running the Master Version¶
This script will install or update to the latest ‘master’ image for Panhandler. This is the version used by users as the official version.
curl -s -k -L http://bit.ly/2xui5gM | bash
You can toggle between the two versions by running one of the curl commands
When switching between dev and latest clear the cache with:
http://localhost:9999/clear_cache
Pruning Images¶
Over time you may accumulate panHandler image files especially if moving between develop and master versions.
You can view the images with:
docker images
If you see multiple panHandler images you can recover disk space using:
docker image prune -a
This will remove all unused images.
To remove all unused containers, networks, images (both dangling and unreferenced), and optionally, volumes:
docker system prune
Playing Skillets from the Repo Detail Page¶
Instead of going back and forth between the repo detail and collections page, you can run skillets from the repo Detail page.
This allows you to refresh and play all from a single page.
Using Environments to Switch between Devices¶
Instead of entering in the target IP address, user and password information when playing a skillet you can create panHandler environments for each target devices. This is especially useful if you are switching between a NGFW and Panorama or have multiple lab or cloud devices for test.
Checkout the panHandler Environment documentation for more details about configuring and using Environments.
Testing with the SkilletBuilder Tools¶
Various Skillet Builder Tools allow for testing and debug. Import into panHandler and look for the Skillet Builder collection.
Key test tools include:
Skillet Test Tool to load yaml-based skillets to a device without Github interactions
Configuration Explorer to look at configuration elements based on XPath
Checking Variable Values with Context¶
Choose View Context
from the top right pulldown in panHandler.
The output will be a current list of variable names and the current value cached in panHandler. This is useful to check variable values especially when testing logic conditionals.
Using Template Skillets to View Values¶
When creating workflows or wanting to see how panHandler handles values, you can create a simple template skillet for testing.
Variables can be added into the template text file as {{ variable }}
and when rendered, the screen output will show
any text include the variable values.
This can also be used to help format any messaging outputs that use variables.
Using Local Variables to Test Workflow Logic¶
In workflow development you may be using a value from a validation, panos, rest or other skillet as input to another skillet. This second skillet may have conditionals based on the output from the first skillet.
To manually create a pass/fail or true/false condition you can temporarily add a type_hint = text variable to the second skillet. When that skillet is played you will see the passed value in the web form and can then edit that value when playing the skillet.
This alleviates the need to constantly update the queried device with different configurations in order to test the workflow and associated logic conditions.
Building and Testing with Appetizer¶
Quickstart¶
Appetizer is a docker image the builds a simple GUI driven web app for any git repository that contains skillets. This makes is very easy to try out automation tools that use skillets.
docker run -it --rm -p 9000:8080 -e 'REPO=https://github.com/PaloAltoNetworks/SkilletBuilder.git' \
-e 'BRANCH=develop' \
--name "Skillet Builder" registry.gitlab.com/panw-gse/as/appetizer
In the above example, the local port 9000 will be used to access the generated Skillet Builder application.
Building and Testing with SLI¶
Install SLI¶
In a terminal/bash shell enter the following to create a virtual python environment and install SLI.
> mkdir {directory name of your choice} > cd {directory from step above} > python3 -m venv ./venv (Create the venv) > source ./venv/bin/activate (Activate the venv) > pip install sli
Use SLI to Perform a Configuration Difference¶
SLI can extract the difference between two configuration files. See instructions above for installing SLI locally on your machine.
To get the difference between two configs in XML format, run the following command:
> sli diff -of xmlAfter entering this command, you will be prompted to enter your NGFW information. After entering the correct information you will receive the configuration differences between the candidate and running configs output as XML (seen below).
Refer to the GitHub page in order to create a new repository and clone it to your local machine. Start with a blank .skillet.yaml file in your text editor/IDE.
Use this basic template to begin populating the file with skillet content:
name: New_Skillet label: Tutorial Skillet description: Skillet template for use with SLI type: panos labels: collection: Unknown variables: - name: description: type_hint: default: '' snippets: - name: xpath: element:For this basic example we will use the edl snippet from the instructions and screenshot above. Add the content for the name, xpath, and element of the snippet.
- name: external-list-820753 xpath: /config/devices/entry[@name="localhost.localdomain"]/vsys/entry[@name="vsys1"] element: |- <external-list> <entry name="edl_name"> <type> <ip> <recurring> <five-minute/> </recurring> <url>http://someurl.com</url> <description>edl_description</description> </ip> </type> </entry> </external-list> cmd: set file: '' template_title: ''For more customization, you can also add variables. For this example we will add a variable to change the name of the edl. Enter the following into the variables section:
variables: - name: edl_name description: name of edl type_hint: text default: ''Next, modify the snippet to use Jinja variable formatting and replace the current edl_name with the variable. It is important to keep the spacing between the curly brackets and the variable name.
- name: external-list-820753 xpath: /config/devices/entry[@name="localhost.localdomain"]/vsys/entry[@name="vsys1"] element: |- <external-list> <entry name="{{ edl_name }}"> <type> <ip> <recurring> <five-minute/> </recurring> <url>http://someurl.com</url> <description>edl_description</description> </ip> </type> </entry> </external-list> cmd: set file: '' template_title: ''Here you can add other desired variables and snippets. You can create a variable for the url and description.
- name: external-list-820753 xpath: /config/devices/entry[@name="localhost.localdomain"]/vsys/entry[@name="vsys1"] element: |- <external-list> <entry name="{{ edl_name }}"> <type> <ip> <recurring> <five-minute/> </recurring> <url>{{ edl_url }}</url> <description>{{ edl_description }}</description> </ip> </type> </entry> </external-list> cmd: set file: '' template_title: ''Don’t forget to add the variables in the variables section.
variables: - name: edl_description description: edl_description type_hint: text default: '' - name: edl_url description: edl_url type_hint: text default: '' - name: edl_name description: edl_name type_hint: text default: ''
Play a Skillet with SLI¶
Clone your skillet in the SLI directory you are currently working in.
> git clone {skillet repo}To load and view the skillets available in the current working directory, type the following:
> sli loadYou can also specify a skillet directory by:
> sli load -sd {skillet directory}To play the skillet, type the following:
> sli configure --name {name of skillet}To specify a directory when playing the skillet enter:
> sli configure -sd {skillet directory} --name {name of skillet}After entering this command, you will be prompted to enter your NGFW information and the values to the variables in the skillet.
Note
If tag_color is a variable in the skillet, you must enter the color number (color1, color2, etc.) and NOT the actual color, otherwise the skillet will not work. Please refer to the color mappings table in the configuration tutorial.
Store User Context in SLI¶
SLI has a built-in context manager that allows data to be stored between commands.
As you play a skillet for the first time, use ‘-uc’ in the command to store the context from the skillet.
> sli configure --name {name of skillet} -ucTo view the context stored in SLI type:
> sli show_contextTo clear the context stored in SLI type:
> sli clear_context
For more in depth instructions on the context manager refer to the SLI Documentation.
Help with SLI¶
In a terminal/bash shell type the following to list all available actions for SLI:
> sli --help

Configuration¶
Overview¶
This tutorial is designed to help the user build an introductory configuration skillet. The tutorial will showcase PanHandler, along with several SkilletBuilder tools that assist the user in creating, editing, and testing skillets. The configuration tutorial will create a simple configuration including:
An IP External Dynamic List (EDL) object
A Tag object
Security rules (Inbound and Outbound) referencing the EDL and tag objects
The video provides an end-to-end perspective for building a configuration skillet as a complement to the documentation content.
Click below to jump to a specific section of the tutorial:
Prerequisites¶
Before moving forward with the tutorial, you will need the following:
NGFW up and running with proper access to GUI and CLI(via SSH)
A GitHub account with access permissions to edit repository content
Please refer to the GitHub page for more info on setting up a repository and importing Skillets into PanHandler.
Docker desktop installed and running on your local machine
For users interested in working through the browser GUI, have PanHandler installed and running on your local machine.
For users interested in working through the command line, have SLI installed on your local machine
SLI is a CLI interface for interacting with Skillets. Please refer to the link above to learn about SLI and get started.
It may also be useful to review the following topics before getting started:
Build the Skillet¶
Create the Configuration in the NGFW¶
Tip
The skillet will only add a single tag to the configuration. However, the GUI shows a color name while the XML data in the NGFW is based on a color number. The use of multiple tag entries is used to extract the color values. So note that in some cases the GUI and XML can use different values and we can use sample configs like this to discover those values.
Commit the changes you just made and save the configuration file. Navigate back to Device > Setup > Operations and ‘Save named configuration snapshot’ again, but name the file something you will remember (ex. skilletbuilder.xml).
Export both the ‘baseline’ configuration file and the file you just saved to your local machine.
![]()
Generate the Skillet from Uploaded Files [Offline Mode]¶
Edit the Initial Config Settings for the Skillet. Here are some suggested inputs for this tutorial:
To continue on with the tutorial click to go to the next section: Add Variables to Snippets
Generate the Skillet from PAN-OS [Online Mode]¶
Edit the Initial Config Settings for the Skillet. Here are some suggested inputs for this tutorial:
Add Variables to Snippets¶
During the configuration of the NGFW, you should have configured an EDL, a tag, and two security policies. Now we can utilize those parts of the configuration to add variables which allow for user input when playing the skillet.
Within the Skillet Editor, you should see the all the settings you input in the previous step. Scroll down to the ‘Snippets’ section; it should be pre-populated with snippets from the configuration files. These snippets represent the pieces of the NGFW configuration that were found to be different between the two files uploaded.
Locate the ‘entry name’ element and enter ‘edl_name’ in both text boxes at the bottom of the page. If you have different text compared to the tutorial or want to name the variable something different, you can make those changes now. Just make sure you take note of what your variables are called. It is best practice to name the variable something that is easily identifiable.
On the right side, click the replace button [seen above] to replace the text with the variable in the editor. This will change the variable to align with Jinja formatting. You should see the double set of curly brackets appear around the variable name.
![]()
Note
Don’t forget to click the ‘Update’ button on both pages to save your work!
Edit Variable Types¶
You should have 6 variables in the Variables section of the Skillet Editor. You also may notice that all of the variable types are ‘text’. This variable type works in some scenarios, but not all. For certain cases, you may want a dropdown menu, radio buttons, or only accept IP addresses/URLs/emails, etc.
We need to change the ‘tag_color’ variable to provide the user with a list of options in the form of a dropdown menu. If you noticed earlier on in the tutorial, we chose red for the tag_color in the GUI, but the color in XML was color1. A user will likely not know the number associated with the color they want, so this will make it easier.
Here is a list of suggested colors associated with their number [key,value].
Tag Color Mappings
Red - color1
Green - color2
Blue - color3
Yellow - color4
Copper - color5
Orange - color6
Purple - color7
Gray - color8
Enter the key,value of the color you want and click the ‘plus’ button to add it. Add 3-6 colors and click ‘Update’.
Please refer to the Variables Page for more information on variable types.
Test and Troubleshoot¶
Play¶
If you receive errors messages, common issues may be:
Snippet load order
Variable typos in the snippet section or not included in the variables section
Invalid input data that passes web form validation but not NGFW validation checks
Continue to edit, push, and test the skillet until it is free of errors and can be loaded onto the NGFW.
Document¶
The final stage is to document key details about the skillet to provide contextual information to the user community.
README.md¶
The skillet repo created has a placeholder README.md and earlier in the tutorial we created a README.md within the skillet directory. The main README gives an overview of the repo for any user viewing the page. The skillet directory README should provide skillet-specific details such as what the skillet does, variable input descriptions, and caveats and requirements.
README.md uses the markdown format. Numerous examples can be found in the skillet files. There is also a wide array of markdown cheat sheets you can find using Google searches. Below are a few common markdown elements you can use in your documentation. Most EDIs can display the user view as you edit the markdown file.
Markdown syntax options
#, ##, ### for header text levels (H1, H2, H3, etc.)
**text** for bold text
*text* or _text_ to underline
1. text to create numbered lists
* text, + text, - text for bullet style lists
[text](url) for inline web links
`test` to highlight a text string
```text block - one or more lines``` to create a highlighted text block
Tip
To view markdown edits in existing GitHub repos, click on the README.md file, then use the
Raw
option to display the output as raw markdown text. From here you can copy-paste or review formatting.Sample README.md file for the tutorial skillet. Paste into the skillet README file and push to Github. View the skillet repo to see the updated page text.
# Sample Configuration Skillet This is used in the training material as part of the tutorial. The skillet has 3 xml elements: * tag: create a tag using inputs for name, description, and color * external-list: create an edl using inputs for name, description, and url * security policies: inbound and outbound security policies referencing the edl and tag names ## variables * tag_name: name of a newly created tag and used in the security rules * tag_description: text field to describe the tag * tag_color: dropdown mapping color names to color numbers (required in the xml configuration) * edl_name: name of the newly created external-list * edl_description: text field used to describe the external-list * edl_url: url used for the external-list The 'recurring' value for the EDL is set to five-minutes. This could be added as a variable but for this example, the value is considered a recommended practice so not configurable in the skillet. The EDL type is set to IP since used in the security policy and is not configurable in the skillet. ## security policy referencing variables The security policy does not have its own variables asking for rule name, zones, or actions. The rules are hardcoded with 'any' for most attributes and action as deny to block traffic matching the EDL IP list. The security rule names use the EDL name followed by '-in' and '-out' to create unique security policies for each EDL. This is denoted in the yaml file with ```{{ edl_name }}``` included in the rule name.Support Policy Text
Skillets are not part of Palo Alto Networks supported product so the policy text is appended to the README file to specify skillets are not supported. Sample text to copy/paste is found in the SkilletBuilder repo README
Live Community¶
Skillets can be shared in the Live community as Community or Personal skillets. Community Skillets are expected to have a higher quality of testing, documentation, and ongoing support. Personal skillets can be shared as-is to create awareness and eventually become upgraded as Community Skillets.
Click here to view the Quickplay Solutions homepage.
Set Commands¶
Overview¶
This tutorial is designed to help the user get familiar with using set commands to bring up and apply basic configs to their NGFW. By the end of this tutorial the user should be able to alter their firewall manually through the Command Line Interface (CLI) with set commands. All set/op commands that can be entered in the CLI manually can also be transformed into an automation playlist in the form of a skillet. This allows the user to run a series of set commands to easily configure their NGFW with just the click of a button. The configuration tutorial will create a simple configuration including:
An IP External Dynamic List (EDL) object
A tag object
Security rules (Inbound and Outbound) referencing the EDL and tag objects
This Basic Config with Set Commands tutorial will show the user how to:
Access and configure the Next Generation Firewall (NGFW) through the web UI and CLI
Capture configuration differences made on the NGFW into set commands and automation skillets
Learn how to use Panhandler tooling
Learn how to use the Skillet Line Interface (SLI) tool on the CLI
Learn the basics of using GitHub and repositories
The video below provides an end-to-end perspective for building a configuration skillet through PanHandler and can be used as a complement to the documentation content.
The video below provides an end-to-end perspective for getting a configuration difference in set command format, saving the config diff to a file and then using the SLI utility tool to load it into an NGFW.
Prerequisites¶
Before moving forward with the tutorial, please ensure the following prerequisites have been fulfilled.
Have an up and running NGFW Virtual Machine (VM)
Familiarity with the core elements of the SkilletBuilder page
A GitHub account with access permissions to edit repository content. Please refer to the following page for guidance on setting up GitHub for this tutorial
Docker installed, active and running on your local machine
Ability to access the NGFW device via the GUI and SSH/CLI
For users wishing to work through the command line have SLI set up and ready to go. Please refer to and follow the steps in the linked SLI page to get started with using SLI within the context of this tutorial
For users wishing to work through the browser UI log into PanHandler and be able to import/run Skillets. For more assistance on using PanHandler tooling within the context of this tutorial please refer to the following page
This tutorial can either be done by reading the document or by watching the tutorial videos. There is a video tutorial for achieving the intended results via use of the PanHandler UI tool and the SLI command line interface tool.
Building Skillets with Set Commands¶
Create the Configuration in the NGFW¶
Before modifying the configuration, ensure you have a snapshot of the before/baseline configuration of your NGFW saved, we will use this saved snapshot to perform an offline configuration difference later. To do this navigate through Device > Setup > Operations > Save named configuration snapshot to save the current NGFW config.
![]()
Once you have hit the add button make necessary changes as seen below and click the OK button.
![]()
Tip
The skillet will only add a single tag to the configuration. However, the GUI shows a color name while the set command is based on a color number. The use of multiple tag entries is used to extract the color values. So note that in some cases the GUI and set commands can use different values and we can use sample configs like this to discover those values.
Finally, configure inbound and outbound security rules referencing the tag and external-list. In order to add security rules please navigate through the following, Policy > Security > Add. Note that the rule names are prepended with the EDL name. In later steps variables are used in the rule names to map the EDL and ensure rule names are unique.
![]()
If you want to be able to generate your set commands skillet in offline mode later in the tutorial, don’t forget to commit and save a modified configuration snapshot of your NGFW here. With your baseline and modified configurations saved you can export the files to your local machine for later use! You can do this by navigating to, Devices > Setup > Operations > Export named configuration snapshot.
![]()
Generate the Set Commands Skillet¶
Note
There is also an option to upload previously saved NGFW XML files manually to the PanHandler SkilletBuilder utility from your local machine. To do this you would have to find the box titled Generate Set Commands From Uploaded Files from the previous step and click on the blue Upload button. On the resulting page titled Skillet Generator you can upload your previously saved NGFW configuration files under the Pre-Configuration and Post-Configuration sections.
You will then end up at another Skillet Generator page where you will need to choose some NGFW configuration options to pull from in a couple of drop-down menus. Under the Pre-Configuration Source menu, choose the baseline configuration. Under the Post Configuration Source menu choose your modified configuration that you want to get the config difference between. After the correct NGFW commit versions are chosen hit Submit.
![]()
After the files are added and submitted, the next stage of the workflow is a web form for the YAML file preamble attributes. Suggested tutorial inputs for this section are as follows:
Skillet ID: tag_edl_tutorial
Skillet Label: Tutorial skillet
Skillet description: The tutorial skillet demonstrates the use of various config snippets and variables
Skillet type:
Template
Branch: Local
Commit Message: Create Tutorial Skillet
![]()
Once everything has been entered, clicking on the blue Submit button results in a screen titled Skillet Editor. This page will showcase parts of the skillet that you just created as well as a snippets section containing all of your set commands from the config diff.
The rendered YAML file contains:
Preamble populated with the web form values
Placeholder variables section
Snippets section with set command entries where each diff is found
Working with Snippets and Variables¶
Upon clicking the edit button you will land at an Edit template snippet page showcasing all the set commands retrieved from the config diff. Here is where we can get into working with a cool templating language called Jinja, to allow for user inputted value substitution within the variables in our skillets. Thankfully with this skillet editor tool there is a very simple and easy way to transform plain text within our set commands into Jinja variables. Click into the small blue Edit button near the bottom right of the screen again.
![]()
Note
Order matters with set commands! The Generate Set CLI Commands skillet won’t always output set commands in the right order. For example it may output the commands in such a way that it will try to load in a security policy before the EDL is created, an example of this is shown in the screenshot below. As you can see the set rulebase security rules set commands are appearing before the set commands that create the edl. This would fail if you input it into the NGFW CLI since the EDL doesn’t exist yet.
Set Commands Out of Order Example
This will take us to a page titled Edit Text, this is where we can make text substitutions for variables. For example if we wanted to change all instances of the text “tag_name” into a jinja variable you would enter in “tag_name” to the left box and then whatever you wanted the variable to be called in the right box. It is best practice to name your variables something identifiable and descriptive. Next hit the Replace button containing 2 arrows pointing in opposite directions to create your variables! Dont forget to click Update twice to confirm and save your changes!
![]()
Note
For the purpose of this Tutorial you should have 6 variables in the variables section of the Skillet Editor. Please refer to the SkilletBuilder variables documentation for a more in depth look at the different kinds of variables and their use cases.
Once the Update button has been pushed and changes have been made you will be brought back to the Skillet Editor screen from before. Here you should see that the previously empty variables section has now been populated with your newly created variables. you can now click into the blue Edit buttons to the right of the variable names to edit their descriptions, names, etc. For example, let’s edit our tag_color variable to contain a dropdown menu option. For your convenience we have provided a handy table below to show what tag colors map to what values.
![]()
Tag Color Mappings
Red - color1
Green - color2
Blue - color3
Yellow - color4
Copper - color5
Orange - color6
Purple - color7
Gray - color8
On the Edit Variable page click on the Variable Type dropdown menu and choose the Dropdown Select option. From here you can type in key:value pairs similar to a dictionary and then click on the + sign on the right to add them as dropdown menu options for your variable color type. Add all the color options you would like and then hit Update at the bottom to save the changes in your variable.
![]()
Back on the Skillet Editor page, we can save all aspects of our generated skillet by clicking the blue Save button at the bottom right of the screen.
![]()
On this page simply scroll down until you find your saved skillet, in this case it should be called Tutorial Skillet. Locate the skillet and click on the Gear icon to inspect the skillets raw YAML data file. Choosing to click into the Gear should allow you to see the fully function skillets YAML file including all generated set commands within as well as the variables that were updated prior.
![]()
At this point you should have a fully functioning set commands skillet! However we aren’t done yet, you always want to be sure to test your skillet for any possible issues before committing it back to your repository. Please refer to the Testing and Troubleshooting section in this tutorial for more guidance on testing methods.
Using SLI to Perform a Configuration Difference¶
In this section we will be going over how to use the SLI tool in the CLI to get a config diff. First head into the folder in which you cloned the SLI repository, activate the venv and perform the pip install command. For more in depth guidance please refer to SLI documentation.
> python3 -m venv ./venv > source ./venv/bin/activate > pip install sli
From here, run the following simple SLI command to get the config diff output in set commands form. accordingly.
> sli diff -of set
Note
It should be noted that you can also get the config diff output in xml and skillet format by changing the keyword after the -of flag
After entering this command you will be prompted to enter your NGFW information, after entering the correct information you will receive all of the config differences output as set commands as can be seen below.
![]()
While in that directory you can run SLI and pass in the .txt file containing all of the set commands to automatically configure the NGFW with all provided set commands.
> sli load_set -uc set_commands.txt
Note
Another handy function that comes with SLI is its ability to locate errors in specific set commands. If any of the set commands entered in through SLI are faulty, SLI will error out and print the faulty set command line for your viewing pleasure!
Tip
You can also add a -v to the end of the above command to make it look like, sli load_set -uc {text_file} -v. This will output all the set commands being passed to the NGFW as they SLI is running in place of the black loading bar showcasing % complete.
At this point all configurations should have been made in your NGFW, simply log in to validate and commit the changes in your NGFW.
Test and Troubleshoot¶
Debug¶
Continue to step through the snippets. If you encounter an error, be sure to check the syntax in the ‘Context’ section. Look for missing quotes ‘”’, colons ‘:’, etc.
Once you have finished debugging, click the orange Dismiss button towards the bottom to close the page.
Play the Skillet¶
On the Repository Details page, click on the Skillet in the ‘Skillets’ section.
![]()
Now you should recognize all the variables that you added earlier on in the tutorial. Add your desired values for the variables. and click Submit.
![]()
After submitting your customized variable names you will reach a page titled Output. Here you will be shown the output of your set command template skillet. You should see all the proper set commands with the respective variable names substituted where they should be.
![]()
If you receive errors messages, common issues may be:
Snippet load order
Set command load order, make sure set commands were loaded in the right order
Variable typos in the snippet section or not included in the variables section
YAML file invalidity
Tip
YAML is notoriously finicky about whitespace and formatting. While it’s a relatively simple structure and easy to learn, it can often also be frustrating to work with. A good reference to use to check that your YAML syntax is up to standard is the YAML Lint site. Test against a live device and fix/tune as needed.
Continue to edit, push, and test the skillet until it is free of errors and performs as expected.
Commit and Save¶
The skillet is now ready to be saved and committed to the GitHub repository. On the Repository Detail for Set_Commands_Tutorial click on the green Push Local Changes button to commit your changes back to your GitHub repository. Doing this will push the local changes you made to your skillet in PanHandler back to GitHub under the chosen branch, in this case the changes will be pushed under the Local branch.
![]()
If the push was successful you should see a green message stating Changes Pushed Upstream! Now navigating back to your GitHub repository for this tutorial, you will want to locate the branches dropdown menu and click on it to expand your branch options. In this case click on Main branch and then navigate to the branch that your changes got pushed to, here it would be Local.
![]()
Document¶
The final stage is to document key details about the skillet to provide contextual information to the user community.
The skillet repo created has a placeholder README.md and earlier in the tutorial we created a README.md within the skillet directory. The main README gives an overview of the repo for any user viewing the page. The skillet directory README should provide skillet-specific details such as what the skillet does, variable input descriptions, and caveats and requirements.
README.md uses the markdown format. Numerous examples can be found in the skillet files. There is also a wide array of markdown cheat sheets you can find using Google searches. Below are a few common markdown elements you can use in your documentation. Most EDIs can display the user view as you edit the markdown file.
Markdown syntax options
#, ##, ### for header text levels (H1, H2, H3, etc.)
**text** for bold text
*text* or _text_ to underline
1. text to create numbered lists
* text, + text, - text for bullet style lists
[text](url) for inline web links
`test` to highlight a text string
```text block - one or more lines``` to create a highlighted text block
Tip
To view markdown edits in existing GitHub repos, click on the README.md file, then use the
Raw
option to display the output as raw markdown text. From here you can copy-paste or review formatting.
Below is a sample README.md file for the tutorial skillet. Paste into the skillet README file and push to Github. View the skillet repo to see the updated page text.
# Sample Configuration Skillet This is used in the training material as part of the tutorial. The skillet has 3 set elements: * tag: create a tag using inputs for name, description, and color * external-list: create an edl using inputs for name, description, and url * security policies: inbound and outbound security policies referencing the edl and tag names ## variables * tag_name: name of a newly created tag and used in the security rules * tag_description: text field to describe the tag * tag_color: dropdown mapping color names to color numbers * edl_name: name of the newly created external-list * edl_description: text field used to describe the external-list * edl_url: url used for the external-list The 'recurring' value for the EDL is set to five-minutes. This could be added as a variable but for this example, the value is considered a recommended practice so not configurable in the skillet. The EDL type is set to IP since used in the security policy and is not configurable in the skillet. ## security policy referencing variables The security policy does not have its own variables asking for rule name, zones, or actions. The rules are hardcoded with 'any' for most attributes and action as deny to block traffic matching the EDL IP list. The security rule names use the EDL name followed by '-in' and '-out' to create unique security policies for each EDL. This is denoted in the yaml file with ```{{ edl_name }}``` included in the rule name.
Live Community¶
Skillets can be shared in the Live community as Community or Personal skillets. Community Skillets are expected to have a higher quality of testing, documentation, and ongoing support. Personal skillets can be shared as-is to create awareness and eventually become upgraded as Community Skillets.
Click here to view the Quickplay Solutions homepage.
SkilletBuilder Support Policy¶
The code and templates in the repo are released under an as-is, best effort, support policy. These scripts should be seen as community supported and Palo Alto Networks will contribute our expertise as and when possible. We do not provide technical support or help in using or troubleshooting the components of the project through our normal support options such as Palo Alto Networks support teams, or ASC (Authorized Support Centers) partners and backline support options. The underlying product used (the VM-Series firewall) by the scripts or templates are still supported, but the support is only for the product functionality and not for help in deploying or using the template or script itself. Unless explicitly tagged, all projects or work posted in our GitHub repository (at https://github.com/PaloAltoNetworks) or sites other than our official Downloads page on https://support.paloaltonetworks.com are provided under the best effort policy.
Validation¶
Overview¶
This tutorial walks through the creation and testing of a validation skillet that will:
Check if NTP servers are configured
Check is password complexity is enabled with a minimum-length >= 12 characters
Check if all configured URL-filtering profiles are blocking the malware category
Check if all ‘allow’ security rules are configured with a security profile or group
Unlike configuration skillets that can start with the difference between two configuration files, validation skillets are more open-ended. Therefore builders need to learn the mechanics of validation skillets to apply to their own use cases. This includes capturing outputs as variables and using them in boolean tests.
The video provides an end-to-end perspective for building a validation skillet as a complement to the documentation content.
Prerequisites¶
It is recommended that users review the following content before building a validation skillet
XML and Skillets to understand syntax, structure, XML parsing, and tools
Jinja and Skillets to understand variables and filters
Custom Jinja Filters to see how custom jinja filters can be applied
Capture Output to see various types of capture outputs to use in tests
Configuration Tutorial Setting Up the Sandbox for EDI, device, and Github readiness
Skillet Builder Tools specifically the Configuration Explorer and Test Tool
You can also view the short video giving a brief overview of validation skillets.
Example .meta-cnc.yaml File¶
This tutorial will be recreating the .meta-cnc.yaml file in the SkilletBuilder repo as the sample_validation_skillet
The same file is also shown below.
show/hide the output .meta-cnc.yaml file
name: sample_validation_skilletbuilder label: Sample Validation Skillet description: | Short set of validations for skilletBuilder training tutorial with ntp check, password complexity, URL filtering for malware, and security allow rules with profiles or groups type: pan_validation labels: collection: - Skillet Builder - Validation variables: - name: placeholder description: Some Parameter default: yes type_hint: hidden snippets: # get ntp server and password complexity objects - name: device_config_file cmd: parse variable: config outputs: - name: ntp_servers capture_object: /config/devices/entry[@name='localhost.localdomain']/deviceconfig/system/ntp-servers - name: password_complexity capture_object: /config/mgt-config/password-complexity # check that ntp servers are configured - name: ntp_servers_test label: configure primary and secondary ntp servers test: | ( ntp_servers | tag_present('primary-ntp-server.ntp-server-address') and ntp_servers | tag_present('secondary-ntp-server.ntp-server-address') ) fail_message: | time server configuration is reccommended to ensure the firewall clock is in sync with external service and logging platforms. pass_message: recommended primary and secondary ntp servers are configured documentation_link: https://iron-skillet.readthedocs.io/en/docs_dev/viz_guide_panos.html#device-setup-services-services # check for password complexity minimum password length - name: password_complexity_test label: configure strong password complexity ( >= 12 chars) test: | ( password_complexity | element_value('enabled') == 'yes' and password_complexity | element_value('minimum-length') >= '12' ) fail_message: | check that password complexity is enabled with a minimum password length of 12 characters pass_message: | password complexity is enabled with a minimum password length of 12 characters documentation_link: https://iron-skillet.readthedocs.io/en/docs_dev/viz_guide_panos.html#device-setup-management-minimum-password-complexity # test that all url-filtering profiles block the category malware - name: url_profile_test cmd: parse variable: config outputs: # get list of all url profiles for debug example - name: url_filtering_profiles capture_list: |- /config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']/profiles/url-filtering/entry/@name # get list of url profiles with malware explicitly set to block # using this model instead of checking for alert, allow, continue - especially with allow not showing in the config - name: url_profiles_block_malware capture_list: |- /config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']/profiles/url-filtering/entry /block/member[text()='malware']/../../@name # get list of all url profiles then filter to profiles not in url_profiles_block_malware - name: url_profiles_not_blocking_malware capture_list: |- /config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']/profiles/url-filtering/entry/@name filter_items: item not in url_profiles_block_malware # check that all url profiles are blocking malware - name: check_all_url_profiles_block_malware label: check that all url profiles block category malware test: url_profiles_not_blocking_malware | length == 0 severity: high fail_message: | url profiles not blocking malware: {{ url_profiles_not_blocking_malware }} pass_message: | all url profiles are currently blocking the category malware documentation_link: https://docs.paloaltonetworks.com/pan-os/9-1/pan-os-admin/url-filtering/configure-url-filtering.html# # test that all allow security policies have a profile or profile-group configured - name: security_policy_test cmd: parse variable: config outputs: # get a list of security policies with a profile or group configured - name: security_policies_with_profile_or_group capture_list: |- /config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1'] /rulebase/security/rules/entry/profile-setting/../@name # get a list of security policies with action allow - name: allow_security_policies_without_profile capture_list: |- /config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1'] /rulebase/security/rules/entry/action[text()='allow']/../@name filter_items: item not in security_policies_with_profile_or_group # check that all allow security policies have a profile or group - name: check_allow_security_policies_have_profile label: check that all allow security policies have a profile or group test: allow_security_policies_without_profile | length == 0 severity: medium fail_message: | allow security policies without a profile or group: {{ allow_security_policies_without_profile }} pass_message: | all allow security policies have a profile or group configured documentation_link: https://docs.paloaltonetworks.com/pan-os/9-1/pan-os-admin/policy/security-profiles/create-a-security-profile-group.html
Skeleton Validation YAML File¶
Similar to the configuration skillet, the initial setup includes the new validation directory in an existing cloned repo or added to a newly created repo in Github. In this directory create placeholder .meta-cnc.yaml and README.md files.
In panHandler under the Skillet Builder collection, run the Skillet YAML File Template skillet. Add in the values for the skillet ID, label, description, and collection name. Select validation as the skillet type.
Paste the output into the placeholder .meta-cnc.yaml file. The preamble contains the values from the web form. The key attribute is the type: pan_validation. This defines this as a validation skillet. You can delete the text under the variables and snippets section.
# skillet preamble information used by panhandler
# ---------------------------------------------------------------------
# unique snippet name
name: validation_tutorial
# label used for menu selection
label: validation to test stuff
description: validation to test - ntp, password complexity, url-filtering to block. malware, and security rules profiles
# type of device configuration
# common types are panorama, panos, and template
# https://github.com/PaloAltoNetworks/panhandler/blob/develop/docs/metadata_configuration.rst
type: pan_validation
# preload static or default-based templates
extends:
# grouping of like snippets for dynamic menu creation in panhandler
labels:
collection:
- Tutorial
Validation Tests¶
The tutorial will step through each validation test including the respective capture output.
Each test will work through the following:
review the configuration to see what we will capture and test
specify the capture output parameters
define the test
add messaging and documentation links to each test
As a reminder, a starter XPath needed for each capture can be found using one or more of the techniques covered in Tools to Find the XPath. In the tutorial I’ll use the CLI option with debug cli on and set cli config-output xml.
All of the initial testing will be done locally using the test tool without pushing the skillet to Github. After all of the tests are working we’ll push to Github and do final review using the panHandler formatted outputs.
NTP Servers¶
The first test will check to see if NTP configuration is present. The CLI command to view the NTP configuration is show deviceconfig system ntp-servers.
admin@homeSkilletFirewall# show deviceconfig system ntp-servers
(container-tag: deviceconfig container-tag: system container-tag: ntp-servers)
((eol-matched: . #t) (eol-matched: . #t) (xpath-prefix: . /config/devices/entry[@name='localhost.localdomain'])
(context-inserted-at-end-p: . #f)) /usr/local/bin/pan_ms_client --config-mode=xml --set-prefix='set deviceconfig
system ' --cookie=5245413957557299 <<'EOF' |sed 2>/dev/null -e 's/devices localhost.localdomain//' |/usr/bin/less -X -E -M
<request cmd="get" obj="/config/devices/entry[@name='localhost.localdomain']/deviceconfig/system/ntp-servers"></request>
EOF
<response status="success" code="19">
<result total-count="1" count="1">
<ntp-servers>
<primary-ntp-server>
<ntp-server-address>0.pool.ntp.org</ntp-server-address>
</primary-ntp-server>
<secondary-ntp-server>
<ntp-server-address>1.pool.ntp.org</ntp-server-address>
</secondary-ntp-server>
</ntp-servers>
</result>
</response>
[edit]
admin@homeSkilletFirewall#
The output shows two key items.
the XPath after ‘obj=’
/config/devices/entry[@name='localhost.localdomain']/deviceconfig/system/ntp-servers
the NTP servers XML element
<ntp-servers>
<primary-ntp-server>
<ntp-server-address>0.pool.ntp.org</ntp-server-address>
</primary-ntp-server>
<secondary-ntp-server>
<ntp-server-address>1.pool.ntp.org</ntp-server-address>
</secondary-ntp-server>
</ntp-servers>
Since the user can set the server address to any value, the focus will be on the tags. In this case the NTP configuration exists if the <ntp-server-address> tags are present under the primary and secondary server settings. This leads to the decision to use the tag_present custom jinja filter with capture_object. The capture object lets us capture the entire XML element to use in the test.
The first part of the snippet is the capture output, where we’ll use capture_object.
- name: device_config_file
cmd: parse
variable: config
outputs:
- name: ntp_servers
capture_object: /config/devices/entry[@name='localhost.localdomain']/deviceconfig/system/ntp-servers
Capture output attribute settings. Let’s outline each item for the first test.
name: contextual name for this capture section
cmd: using parse to parse the config file
variable: set to config to parse the config file as the raw input content
outputs: where we can define one or more output variables
name: unique variable where the NTP configuration object is stored
capture_object: XPath for the NTP configuration
Now with the ntp-servers dict object, we can craft the test and associated messages and links. These are added to the snippets section of the .meta-cnc.yaml file.
- name: ntp_servers_test
label: configure primary and secondary ntp servers
test: |
(
ntp_servers | tag_present('primary-ntp-server.ntp-server-address')
and ntp_servers | tag_present('secondary-ntp-server.ntp-server-address')
)
fail_message: |
time server configuration is reccommended to ensure the firewall clock is in sync with external service and logging
platforms.
pass_message: recommended primary and secondary ntp servers are configured
documentation_link: https://iron-skillet.readthedocs.io/en/docs_dev/viz_guide_panos.html#device-setup-services-services
Test section attribute settings. Let’s outline each item as part of the first test.
name: unique name for the test
label: panHandler test results display line item
test: test performed; this example uses and to test two items
fail_message: what to display if the test fails
pass_message: what to display if the test passes
documentation link: helper content specific to the test
Let’s look at the test attribute in more detail. Everything else should be fairly straightforward.
test: |
(
ntp_servers | tag_present('primary-ntp-server.ntp-server-address')
and ntp_servers | tag_present('secondary-ntp-server.ntp-server-address')
)
Let’s break it down.
The first mini test uses the ntp_servers capture object as the input. The check is after the pipe ‘|’ using a custom filter ‘tag_present’. The dot notation is used to step down into the object to primary-ntp-server to get to the tag of interest <ntp-server-address>. If this tag is present the test returns True.
The second mini test performs an identical check but looks at the secondary-ntp-server portion of the configuration to see if the <ntp-server-address> tag is part of the configuration. If this tag is present the test returns True.
In this case we want both servers to be configured so the and is used with outer parentheses to combine each isolated test into one boolean test output. The test is True only if both mini tests return True. A True will display the pass_message and a False will output the fail message.
The pipe after ‘test:’ is a formatting option to allow for multiline inputs. Common for aggregate tests.
With the capture output and test put together we get the following in the snippets section.
- name: device_config_file
cmd: parse
variable: config
outputs:
- name: ntp_servers
capture_object: /config/devices/entry[@name='localhost.localdomain']/deviceconfig/system/ntp-servers
# check that ntp servers are configured
- name: ntp_servers_test
label: configure primary and secondary ntp servers
test: |
(
ntp_servers | tag_present('primary-ntp-server.ntp-server-address')
and ntp_servers | tag_present('secondary-ntp-server.ntp-server-address')
)
fail_message: |
time server configuration is reccommended to ensure the firewall clock is in sync with external service and logging
platforms.
pass_message: recommended primary and secondary ntp servers are configured
documentation_link: https://iron-skillet.readthedocs.io/en/docs_dev/viz_guide_panos.html#device-setup-services-services
Copy this text to the snippets section of the .meta-cnc.yaml file. Our first test is complete.
Now copy the entire .meta-cnc.yaml text and paste into Skillet Content section of the Skillet Test Tool. You should have NGFW access in your sandbox and can use Running Configuration as the Online Configuration Source. Click Submit to play the skillet.
Look at the output from the first section, ‘Execution Results’. This shows what would be sent back to the application to present the results and is used for debugging purposes.
{
"snippets": {
"ntp_servers_test": true
},
"pan_validation": {
"ntp_servers_test": {
"results": true,
"label": "configure primary and secondary ntp servers",
"severity": "low",
"documentation_link": "https://iron-skillet.readthedocs.io/en/docs_dev/viz_guide_panos.html#device-setup-services-services",
"test": "(\nntp_servers | tag_present('primary-ntp-server.ntp-server-address')\nand ntp_servers | tag_present('secondary-ntp-server.ntp-server-address')\n)\n",
"output_message": "recommended primary and secondary ntp servers are configured"
}
}
}
Under pan_validation.ntp_servers_test you see the results, items read from the YAML file, and an output message selected based on True or False results.
The second section of the test output is the YAML text. The third section shows all of the variable values.
hostname = "myFirewall"
choices = "choices"
snippets = ""
device_config_file = {
"results": "success",
"changed": false
}
ntp_servers = {
"ntp-servers": {
"primary-ntp-server": {
"ntp-server-address": "0.pool.ntp.org"
},
"secondary-ntp-server": {
"ntp-server-address": "1.pool.ntp.org"
}
}
}
ntp_servers_test = {
"results": true,
"label": "configure primary and secondary ntp servers",
"severity": "low",
"documentation_link": "https://iron-skillet.readthedocs.io/en/docs_dev/viz_guide_panos.html#device-setup-services-services",
"test": "(\nntp_servers | tag_present('primary-ntp-server.ntp-server-address')\nand ntp_servers | tag_present('secondary-ntp-server.ntp-server-address')\n)\n",
"output_message": "recommended primary and secondary ntp servers are configured"
}
This allows you to see the ntp_servers object content read from the NGFW. In this case the servers are configured. An empty value is typically the result of an empty NGFW configuration or an incorrect capture_object XPath. If the test results aren’t as expected review the running configuration to make sure it aligns with the context output.
This test looks good so lets move on to the next one.
Password Complexity¶
For this test we’ll just cover the highlights. Review the previous NTP servers test for attribute explanations.
This test checks to see if password complexity is enabled and if the minimum password length is >=12. The CLI command to view the NTP configuration is show deviceconfig system ntp-servers.
admin@homeSkilletFirewall# show mgt-config password-complexity
(container-tag: mgt-config container-tag: password-complexity)
((eol-matched: . #t) (eol-matched: . #t) (xpath-prefix: . /config) (context-inserted-at-end-p: . #f))
/usr/local/bin/pan_ms_client --config-mode=xml --set-prefix='set mgt-config ' --cookie=9688686339792135 <<'EOF'
|sed 2>/dev/null -e 's/devices localhost.localdomain//' |/usr/bin/less -X -E -M
<request cmd="get" obj="/config/mgt-config/password-complexity"></request>
EOF
<response status="success" code="19">
<result total-count="1" count="1">
<password-complexity>
<enabled>yes</enabled>
<minimum-length>12</minimum-length>
<minimum-uppercase-letters>1</minimum-uppercase-letters>
<minimum-lowercase-letters>1</minimum-lowercase-letters>
<minimum-numeric-letters>1</minimum-numeric-letters>
<minimum-special-characters>1</minimum-special-characters>
<block-username-inclusion>yes</block-username-inclusion>
<password-history-count>24</password-history-count>
<new-password-differs-by-characters>3</new-password-differs-by-characters>
</password-complexity>
</result>
</response>
[edit]
admin@homeSkilletFirewall#
The output shows two key items.
the XPath after ‘obj=’
/config/mgt-config/password-complexity
the pasword-complexity XML element
<password-complexity>
<enabled>yes</enabled>
<minimum-length>12</minimum-length>
<minimum-uppercase-letters>1</minimum-uppercase-letters>
<minimum-lowercase-letters>1</minimum-lowercase-letters>
<minimum-numeric-letters>1</minimum-numeric-letters>
<minimum-special-characters>1</minimum-special-characters>
<block-username-inclusion>yes</block-username-inclusion>
<password-history-count>24</password-history-count>
<new-password-differs-by-characters>3</new-password-differs-by-characters>
</password-complexity>
In this example we’re explicitly looking for the enabled and minimum-length settings. Instead of tags we’re focused on the element text values: the ‘yes’ between the <enabled> tags and the ‘12’ between the <minimum-length> tags.
Design choices: we could create two unique capture_value outputs for each item with more granular XPaths but in this case I’ve opted to test items from a single password-complexity object. This is useful if I later decide to add more tests for various password-complexity settings.
- name: device_config_file
cmd: parse
variable: config
outputs:
- name: ntp_servers
capture_object: /config/devices/entry[@name='localhost.localdomain']/deviceconfig/system/ntp-servers
- name: password_complexity
capture_object: /config/mgt-config/password-complexity
In this example I’ve added the output for password_complexity to the ntp_servers output. This shows how you can add more captures under one outputs attribute. You could also create a new capture section. We’ll do that with the next test.
Add a new test section. This one is called password_complexity_test and also uses two mini tests to get an aggregate result. These could optionally be two unique tests with their own test results depending on design choices.
- name: password_complexity_test
label: configure strong password complexity ( >= 12 chars)
test: |
(
password_complexity | element_value('enabled') == 'yes'
and password_complexity | element_value('minimum-length') >= '12'
)
fail_message: |
check that password complexity is enabled with a minimum password length of 12 characters
pass_message: |
password complexity is enabled with a minimum password length of 12 characters
documentation_link: https://iron-skillet.readthedocs.io/en/docs_dev/viz_guide_panos.html#device-setup-management-minimum-password-complexity
Let’s break it down
The first test uses element_value and the tag of interest, enabled. Since this is at the top of the captured object, no dot notation stepping down the configuration is needed. The expression == yes is used for the test. If enabled is ‘yes’ the test result is True. Otherwise we get a False.
The second test is similar using the minimum-length tag. This expression checks >= 12 and if the configuration setting meets this condition, a True result is returned.
Copy the password-complexity outputs lines and the new test into the .meta-cnc.yaml file. Then copy the full skillet into the Test Tool and run.
Note
make sure the YAML file alignments are correct or you’ll get errors running the skillet.
You’ll now see both test results in the output.
{
"snippets": {
"ntp_servers": true,
"password_complexity_test": true
},
"pan_validation": {
"ntp_servers": {
"results": true,
"label": "configure primary and secondary ntp servers",
"severity": "low",
"documentation_link": "https://iron-skillet.readthedocs.io/en/docs_dev/viz_guide_panos.html#device-setup-services-services",
"test": "(\nntp_servers | tag_present('primary-ntp-server.ntp-server-address')\nand ntp_servers | tag_present('secondary-ntp-server.ntp-server-address')\n)\n",
"output_message": "recommended primary and secondary ntp servers are configured"
},
"password_complexity_test": {
"results": true,
"label": "configure strong password complexity ( >= 12 chars)",
"severity": "low",
"documentation_link": "https://iron-skillet.readthedocs.io/en/docs_dev/viz_guide_panos.html#device-setup-management-minimum-password-complexity",
"test": "(\npassword_complexity | element_value('enabled') == 'yes'\nand password_complexity | element_value('minimum-length') >= '12'\n)\n",
"output_message": "password complexity is enabled with a minimum password length of 12 characters"
}
}
}
Also review the Full Context section of the output to see the password_complexity captured object.
password_complexity = {
"password-complexity": {
"enabled": "yes",
"minimum-length": "12",
"minimum-uppercase-letters": "1",
"minimum-lowercase-letters": "1",
"minimum-numeric-letters": "1",
"minimum-special-characters": "1",
"block-username-inclusion": "yes",
"password-history-count": "24",
"new-password-differs-by-characters": "3"
}
}
You can modify the NGFW settings and see the changes in the output here. A null value may indicate an empty running configuration or incorrect XPath for this capture.
This completes the second test.
URL-Filtering and Malware¶
The prior tests were looking at very specific items: primary and secondary NTP settings and password-complexity configuration. This test however will query across a set of URL-filtering objects, their names unknown. So the logic is a bit more fuzzy.
The goal is to get a list of all URL-filtering profiles, specifically the names. Then get the names of all profiles with the category malware explicitly set to block. The difference between the two lists of names are the URL-filtering profiles that do not have malware set to block. For this test we’ll use a built-in Jinja Filter and capture_list for the output.
The CLI command to view the URL-filtering profile configuration is show profiles url-filtering. The output XML element has been edited to only show the malware category for each profile. Actual output will be much longer.
admin@homeSkilletFirewall# show profiles url-filtering
(container-tag: profiles container-tag: url-filtering)
((eol-matched: . #t) (eol-matched: . #t) (eol-matched: . #t) (xpath-prefix: .
/config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1'])
(context-inserted-at-end-p: . #f)) /usr/local/bin/pan_ms_client --config-mode=xml --set-prefix='set profiles
' --cookie=2581626760981804 <<'EOF' |sed 2>/dev/null -e 's/devices localhost.localdomain//'
|/usr/bin/less -X -E -M <request cmd="get"
obj="/config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']/profiles/url-filtering"></request>
EOF
<response status="success" code="19">
<result total-count="1" count="1">
<url-filtering>
<entry name="Outbound-URL">
<block>
<member>malware</member>
</block>
</entry>
<entry name="Alert-Only-URL">
<alert>
<member>malware</member>
</alert>
</entry>
<entry name="Exception-URL">
<block>
<member>malware</member>
</block>
</entry>
</url-filtering>
</result>
</response>
The output shows two key items.
the XPath after ‘obj=’
/config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']/profiles/url-filtering
the URL-filtering XML element
<url-filtering>
<entry name="Outbound-URL">
<block>
<member>malware</member>
</block>
</entry>
<entry name="Alert-Only-URL">
<alert>
<member>malware</member>
</alert>
</entry>
<entry name="Exception-URL">
<block>
<member>malware</member>
</block>
</entry>
</url-filtering>
In this example we want to capture a list of all profile names. Then we want to create a list of profiles where <block> <member> includes malware. This requires Parsing XML to capture the lists.
The first step is to put the XPath into the Configuration Explorer Tool and begin to tune the outputs. With an active connection to the NGFW, use Online mode and enter the XPath into the XPath Query field. The output will show the XML element, the same output as the CLI show command. The goal is is to make sure we have a solid starting point.
Now run the query again with /entry/@name appended to the XPath. The Execution results will be a list of profile names.
xpath: /config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']/profiles/url-filtering/entry/@name
========================================================================================================================
xml:
List of items:
Outbound-URL
Alert-Only-URL
Exception-URL
=========================================================================================================================
json:
[
"Outbound-URL",
"Alert-Only-URL",
"Exception-URL"
]
This gets us closer to what we need for testing: a list of all profile names.
While here we also want to create an XPath query that only returns the names with malware set to block. This requires both a filter to limit the results and then walking back up the tree to get the names. Time to experiment.
Appending the base XPath with ‘/block’ will return all of the <block> config elements. But we don’t have the entry names yet. Then going one level down by adding ‘/member’ will show the member entries. Now append the output with ‘text()’ to only see the category names. This is how we can step through the tree and tune the capture.
Time to filter. Remove ‘/text()’ from the end and instead use in a filter with [text()=’malware’] after member. The output is now just <member>malware</member> so we’ve limited to these config elements. But what are the entry names?
xpath: /config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']
/profiles/url-filtering/entry/block/member[text()='malware']
=======================================================================================
xml:
List of items:
<member>malware</member>
<member>malware</member>
The last part of the query is to step back up the tree to the <entry> level and grab the names. This requires the ‘..’ notation similar to returning up a level in a Linux directory path. Looking back at the XML element we have to go up two levels: <member> to <block>, <block> to <entry>. So we’ll append ‘/../../’ to the end of the XPath. Since we only want the names, append again with /@name. Yes this is a long XPath query string.
xpath: /config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']/
profiles/url-filtering/entry/block/member[text()='malware']/../../@name
========================================================================================
xml:
List of items:
Outbound-URL
Exception-URL
So the output we need is based on the XML query above to get the list of profile names with malware = block. Now that we have the two queries, time to get back to our skillet.
- name: url_profile_test
cmd: parse
variable: config
outputs:
# get list of url profiles with malware explicitly set to block
# using this model instead of checking for alert, allow, continue - especially with allow not showing in the config
- name: url_profiles_block_malware
capture_list: |-
/config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']/profiles/url-filtering/entry
/block/member[text()='malware']/../../@name
# get list of all url profiles then filter to profiles not in url_profiles_block_malware
- name: url_profiles_not_blocking_malware
capture_list: |-
/config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']/profiles/url-filtering/entry/@name
filter_items: item not in url_profiles_block_malware
For this validation we’ll need two outputs. The first, url_profiles_block_malware captures the list of all URL-filtering profiles that have malware as block. The capture_list XPath should look familiar.
The second uses the capture_list for all the profile names. The variable name is url_profiles_not_blocking_malware so we need to filter the full list and exclude items with malware set to block. Here we use filter_items to step through all of the names and if its NOT in the url_profiles_block_malware list, add it to this one. Thus we’re comparing two lists to find the delta. That delta is our list of interest for the test.
The test looks like
- name: check_all_url_profiles_block_malware
label: check that all url profiles block category malware
test: url_profiles_not_blocking_malware | length == 0
severity: high
fail_message: |
url profiles not blocking malware: {{ url_profiles_not_blocking_malware }}
pass_message: |
all url profiles are currently blocking the category malware
documentation_link: https://docs.paloaltonetworks.com/pan-os/9-1/pan-os-admin/url-filtering/configure-url-filtering.html#
You’ll notice the test is very simple. If the url_profiles_not_blocking_malware list has a length == 0 (meaning empty) then the test passes. If any profiles show up in this list then they don’t have malware set to block and cause a test Fail. We also use the list variable in the fail_message to show what profiles caused the test to fail.
Now copy the capture output and test sections and paste at the bottom of the .meta-cnc.yaml file. This is the third test. Use the test tool to see the outputs.
Tip
You can create a scratch skillet file with only the capture and test currently begin developed. This is pasted into the test tool and removes any clutter from other tests. Once the test is properly configured you can copy back to the master validation YAML file.
The Execution Results show a test fail. This is a good thing since the Alert-Only-URL profile doesn’t block malware. I know this is the bad apple by looking at the output_message line and the profile name is listed.
{
"snippets": {
"check_all_url_profiles_block_malware": false
},
"pan_validation": {
"check_all_url_profiles_block_malware": {
"results": false,
"label": "check that all url profiles block category malware",
"severity": "high",
"documentation_link": "https://docs.paloaltonetworks.com/pan-os/9-1/pan-os-admin/url-filtering/configure-url-filtering.html#",
"test": "url_profiles_not_blocking_malware | length == 0",
"output_message": "url profiles not blocking malware: ['Alert-Only-URL']"
}
}
}
The other data of interest in the Full Context section is the capture values for the two outputs. Useful for debugging when the results are not as expected.
url_profiles_block_malware = [
"Outbound-URL",
"Exception-URL"
]
url_profiles_not_blocking_malware = [
"Alert-Only-URL"
]
You can see the lists captured for each output entry.
Tip
If you want to see all of the profile names you can use a capture_list output with the list of names exluding any filters. Even without a test association, the list of names will appear in the debug output as part of the Full Context.
Proper testing and tuning would include changing the settings in the NGFW and seeing the output results.
Security Rules with Profiles¶
The last test looks across all ‘allow’ security rules to see which have a profile or profile-group configured. Creating this test is similar to the URL-filtering example.
The goal is to get a list of all ‘allow’ security rules, specifically the names. Then get the names of all rules with a profile or profile-group. The difference between the two lists of names will gives us the rules of interest, the allow rules without a profile or group. For this test we’ll again use a built-in Jinja Filter and capture_list for the output.
The CLI command to view the security rules is show rulebase security rules. The output XML element based on HomeSkillet has been edited to only show the name, action, and profile settings. Actual output will be much longer. The example has also modified the HomeSkillet configuration by removing the profile settings from the rule HS-non-def-web-ports and using profiles in the rule HS-find-non-def-apps. These changes help show the different between profiles and groups while giving us a ‘bad rule’ that will fail the test.
admin@homeSkilletFirewall# show rulebase security rules
(container-tag: rulebase container-tag: security container-tag: rules)
((eol-matched: . #t) (eol-matched: . #t) (eol-matched: . #t) (xpath-prefix: .
/config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1'])
(context-inserted-at-end-p: . #f))
/usr/local/bin/pan_ms_client --config-mode=xml --set-prefix='set rulebase security '
--cookie=7811212055193400 <<'EOF' |sed 2>/dev/null -e 's/devices localhost.localdomain//'
|/usr/bin/less -X -E -M <request cmd="get"
obj="/config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']/rulebase/security/rules"></request>
EOF
<response status="success" code="19">
<result total-count="1" count="1">
<rules>
<entry name="Outbound Block Rule">
<action>deny</action>
</entry>
<entry name="Inbound Block Rule">
<action>deny</action>
</entry>
<entry name="DNS Sinkhole Block">
<action>deny</action>
</entry>
<entry name="HS-block-quic">
<action>deny</action>
</entry>
<entry name="HS-no-unknown-URL-xfer">
<profile-setting>
<group>
<member>Outbound-Unknown-URL</member>
</group>
</profile-setting>
<action>allow</action>
</entry>
<entry name="HS-allow-outbound">
<action>allow</action>
<profile-setting>
<group>
<member>Outbound</member>
</group>
</profile-setting>
</entry>
<entry name="HS-non-def-SSL-ports">
<action>allow</action>
<profile-setting>
<group>
<member>Outbound</member>
</group>
</profile-setting>
</entry>
<entry name="HS-non-def-web-ports">
<action>allow</action>
</entry>
<entry name="HS-find-non-def-apps">
<action>allow</action>
<profile-setting>
<profiles>
<virus>
<member>Outbound-AV</member>
</virus>
<vulnerability>
<member>Outbound-VP</member>
</vulnerability>
</profiles>
</profile-setting>
</entry>
</rules>
</result>
</response>
[edit]
admin@homeSkilletFirewall#
The output shows two key items.
the XPath after ‘obj=’
/config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']/rulebase/security/rules
the Security Rule XML element
<rules>
<entry name="Outbound Block Rule">
<action>deny</action>
</entry>
<entry name="Inbound Block Rule">
<action>deny</action>
</entry>
<entry name="DNS Sinkhole Block">
<action>deny</action>
</entry>
<entry name="HS-block-quic">
<action>deny</action>
</entry>
<entry name="HS-no-unknown-URL-xfer">
<profile-setting>
<group>
<member>Outbound-Unknown-URL</member>
</group>
</profile-setting>
<action>allow</action>
</entry>
<entry name="HS-allow-outbound">
<action>allow</action>
<profile-setting>
<group>
<member>Outbound</member>
</group>
</profile-setting>
</entry>
<entry name="HS-non-def-SSL-ports">
<action>allow</action>
<profile-setting>
<group>
<member>Outbound</member>
</group>
</profile-setting>
</entry>
<entry name="HS-non-def-web-ports">
<action>allow</action>
</entry>
<entry name="HS-find-non-def-apps">
<action>allow</action>
<profile-setting>
<profiles>
<virus>
<member>Outbound-AV</member>
</virus>
<vulnerability>
<member>Outbound-VP</member>
</vulnerability>
</profiles>
</profile-setting>
</entry>
</rules>
In this example we want to capture a list of all action=allow rule names. Then we want to create a list of rules where <profile-setting> is present. Then we’ll compare these two lists. This requires Parsing XML to capture the lists.
The first step is to put the XPath into the Configuration Explorer Tool and begin to tune the outputs. With an active connection to the NGFW, use Online mode and enter the XPath into the XPath Query field. The output will show the XML element same output as the CLI show command. The goal is is to make sure we have a solid starting point.
Now run the query again with /entry/@name appended to the XPath. The Execution results will be a list of all security rules names.
xpath: /config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']
/rulebase/security/rules/entry/@name
======================================================================================
xml:
List of items:
Outbound Block Rule
Inbound Block Rule
DNS Sinkhole Block
HS-block-quic
HS-no-unknown-URL-xfer
HS-allow-outbound
HS-non-def-SSL-ports
HS-non-def-web-ports
HS-find-non-def-apps
This is all of the rules but first we only want the allow rules.
For the action=allow we’ll look one down level by removing ‘/@name’ and adding ‘/action’ to the XPath. The output is a list of <action> elements, a mix of deny and allow.
xpath: /config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']
/rulebase/security/rules/entry/action
=====================================================================================
xml:
List of items:
<action>deny</action>
<action>deny</action>
<action>deny</action>
<action>deny</action>
<action>allow</action>
<action>allow</action>
<action>allow</action>
<action>allow</action>
<action>allow</action>
Next we filter to only capture the ‘allow’ elements by appending action with [text()=’allow’]
xpath: /config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']
/rulebase/security/rules/entry/action[text()='allow']
=====================================================================================
xml:
List of items:
<action>allow</action>
<action>allow</action>
<action>allow</action>
<action>allow</action>
<action>allow</action>
So at this stage we’re only grabbing allow elements but we need their entry names. Next we walk back up the tree one level with a /../ and append the query with @name to only return the names.
xpath: /config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']
/rulebase/security/rules/entry/action[text()='allow']/../@name
=====================================================================================
xml:
List of items:
HS-no-unknown-URL-xfer
HS-allow-outbound
HS-non-def-SSL-ports
HS-non-def-web-ports
HS-find-non-def-apps
We now have the XPath query to use as one part of our capture output.
The second list isn’t looking for names or specific values but checking if the <profile-setting> tag is present. This tag only appears in the configuration if a profile or group exists.
xpath: /config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']
/rulebase/security/rules/entry/profile-setting
======================================================================================
xml:
List of items:
<profile-setting>
<group>
<member>Outbound-Unknown-URL</member>
</group>
</profile-setting>
<profile-setting>
<group>
<member>Outbound</member>
</group>
</profile-setting>
<profile-setting>
<group>
<member>Outbound</member>
</group>
</profile-setting>
<profile-setting>
<profiles>
<virus>
<member>Outbound-AV</member>
</virus>
<vulnerability>
<member>Outbound-VP</member>
</vulnerability>
</profiles>
</profile-setting>
Close but we want the names of the rules with the profile-settings. To get this we’ll use /../ to come up a level from <profile-setting> to <entry> and then output the entry names by appending @name to the XPath.
xpath: /config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']
/rulebase/security/rules/entry/profile-setting/../@name
=====================================================================================
xml:
List of items:
HS-no-unknown-URL-xfer
HS-allow-outbound
HS-non-def-SSL-ports
HS-find-non-def-apps
This output is all of the security rules with a profile-setting. Now we have the XPath query to use in the capture output for the profile-setting rules.
Now that we have both queries, time to get back to our skillet.
# test that all allow security policies have profile-settings configured
- name: security_policy_test
cmd: parse
variable: config
outputs:
# get a list of security policies with a profile or group configured
- name: security_policies_with_profile_or_group
capture_list: |-
/config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']
/rulebase/security/rules/entry/profile-setting/../@name
# get a list of security policies with action allow
- name: allow_security_policies_without_profile
capture_list: |-
/config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']
/rulebase/security/rules/entry/action[text()='allow']/../@name
filter_items: item not in security_policies_with_profile_or_group
For this validation we’ll need two outputs. The first, security_policies_with_profile_or_group captures the list of all security policies with a profile setting.
The second uses the capture_list for all of the allow security rules. The variable name is allow_security_policies_without_profile so we need to filter the full list of rules down to the items that are not in the security_policies_with_profile_or_group list. The delta is our list of interest showing which allow rules don’t have a profile setting.
The test looks like
# check that all allow security policies have a profile or group
- name: check_allow_security_policies_have_profile
label: check that all allow security policies have a profile or group
test: allow_security_policies_without_profile | length == 0
severity: medium
fail_message: |
allow security policies without a profile or group: {{ allow_security_policies_without_profile }}
pass_message: |
all allow security policies have a profile or group configured
documentation_link: https://docs.paloaltonetworks.com/pan-os/9-1/pan-os-admin/policy/security-profiles/create-a-security-profile-group.html
You’ll notice the test is very simple. If the allow_security_policies_without_profile list has a length == 0 (meaning empty) then the test passes. If any rules show up in this list then they don’t have a profile-setting and cause a test Fail. We also use the list variable in the fail_message to show what rules caused the test to fail.
Now copy the capture output and test sections and paste at the bottom of the .meta-cnc.yaml file. This is the final test. Now use the test tool to see the outputs.
The Execution Results show a test fail. This is a good thing since the HS-non-def-web-ports rule doesn’t have a profile setting. I know this is the bad apple by looking at the output_message line and the security rule name is listed.
{
"snippets": {
"check_allow_security_policies_have_profile": false
},
"pan_validation": {
"check_allow_security_policies_have_profile": {
"results": false,
"label": "check that all allow security policies have a profile or group",
"severity": "medium",
"documentation_link": "https://docs.paloaltonetworks.com/pan-os/9-1/pan-os-admin/policy/security-profiles/create-a-security-profile-group.html",
"test": "allow_security_policies_without_profile | length == 0",
"output_message": "allow security policies without a profile or group: ['HS-non-def-web-ports']"
}
}
}
The other data of interest is the capture values for the two outputs. Useful for debugging when the results are not as expected.
security_policies_with_profile_or_group = [
"HS-no-unknown-URL-xfer",
"HS-allow-outbound",
"HS-non-def-SSL-ports",
"HS-find-non-def-apps"
]
allow_security_policies_without_profile = [
"HS-non-def-web-ports"
]
You can see the lists captured for each output entry.
Proper testing and tuning would include changing the settings in the NGFW and viewing the output results.
Push to Github and Test in panHandler¶
Now the .meta-cnc.yaml file has the four tests ready to go. Push the skillet to Github and import into panHandler. Run the skillet to view results.
Note
The Severity settings are optional and added into the tests for demonstration. Severity can be used in the tests or left as the default ‘low’.
Edit the README.md Docs¶
The final step as with any skillet is to add the Documentation in the skillet REAMD.md file.
Workflow¶
Overview¶
This tutorial is aimed at novice skillet developers who want to build a sample workflow skillet. Workflows are a simple way to tie together multiple skillets into one chain of execution. This solution type is preferred in these specific use cases:
Joining together skillets of multiple types
Breaking the automation into many steps with human input in the middle
This workflow tutorial considers both of these use cases when developing a solution that chains a validation skillet, a configuration skillet, and a template skillet into one cohesive workflow solution.
- Click below to jump to a specific section of the tutorial:
Prerequisites¶
Before moving forward with the tutorial, you will need to do the following:
Create a GitHub repository, instructions here
Open your repository in a text editor or IDE
Install PanHandler using Docker, instructions here
Install SLI using a Python virtual environment, instructions here
Deploy a Next Generation Firewall for testing with proper access to GUI and CLI(via SSH)
Design the Solution¶
Designing the execution flow of a workflow skillet is one of the most important aspect of its development.
From a high level, workflow skillets begin execution by prompting the user for input, which get saved as variables. Once the user finishes with the prompted workflow menu, each workflow sub-skillet gets executed in a defined sequence. This sequence can be static, or it can conditionally change depending on the user’s input.
As a result, when designing a workflow, you must think about:
Overall sequence of sub-skillets
Conditional execution of each sub-skillet
User-facing menu options
Design this Tutorial’s Solution¶
The sequence of this tutorial’s workflow solution is described in the image below.
In this tutorial, you will walk through the steps to create the main workflow skillet. It is assumed that the individual sub-skillets that the workflow calls are previously developed. You can use developed skillets from the Quickplay Solution’s LIVEcommunity page; you can use GitHub submodules to incorporate developed skillets; or you can develop your own skillets. For information on developing other skillet types, please look through the tutorials under the Tutorials section.
Note
You can NOT call a workflow skillet inside of a workflow skillet.
The last design decision for this workflow solution is the user-facing workflow menu options. Since the automation will be accessing a Next Generation Firewall (NGFW), it will need access credentials. In addition, the solution will need configuration details specific to the configuration skillet. Lastly, it will need to know when the user wants the validation skillets run.
With this information, we can outline what the menu options should look like:
Build the Skillet¶
The following steps take the user from creating the GitHub repo, through generating and editing the main skillet, to a final push of the main skillet content back to the created repo.
Set-up the Directory Structure¶
In your text editor/IDE of choice, open the repository’s root directory, and add a subdirectory/folder, which will contain all of the skillet contents (eg. edl_xml_policy_workflow). Inside of this newly created folder, add the following files:
An empty
workflow_tutorial.skillet.yaml
file for the main workflow skillet contents (to be populated later)An empty
README.md
file (to be populate later)
config_xml_edl_policy.skillet.yaml
file with the configuration sub-skillet contentsShow/Hide the configuration skillet contents
# skillet preamble information used by panhandler # --------------------------------------------------------------------- # unique snippet name name: config_xml_edl_policy # label used for menu selection label: Sample SkilletBuilder skillet with EDL, tag, and security policy description: Used by SkilletBuilder to demonstrate skillet creation and loading and cross-element variables # type of device configuration # common types are panorama, panos, and template type: panos # grouping of like snippets for dynamic menu creation in panhandler labels: collection: - Skillet Builder # --------------------------------------------------------------------- # end of preamble section # variables section # --------------------------------------------------------------------- # variables used in the configuration templates # type_hint defines the form field used by panhandler # type_hints examples include text, ip_address, or dropdown variables: # variables used for connection with NGFW; type_hint of hidden since # the values are cached in the context after the workflow skillet - name: TARGET_IP description: NGFW IP or Hostname default: 192.168.55.10 type_hint: hidden - name: TARGET_USERNAME description: NGFW Username default: admin type_hint: hidden - name: TARGET_PASSWORD description: NGFW Password default: admin type_hint: hidden - name: edl_name description: name of the external list default: my_edl type_hint: text - name: edl_description description: description of the external list default: this is an ip block list type_hint: text - name: edl_url description: external list url default: http://someurl.com type_hint: text - name: tag_name description: tag name default: tag name type_hint: text - name: tag_description description: tag description default: tag description type_hint: text - name: tag_color description: tag color default: red type_hint: dropdown dd_list: - key: blue value: color3 - key: green value: color2 - key: orange value: color6 - key: red value: color1 # --------------------------------------------------------------------- # end of variables section # snippets section # --------------------------------------------------------------------- # snippets used for api configuration including xpath and element as file name # files will load in the order listed snippets: - name: object_tag xpath: /config/devices/entry[@name="localhost.localdomain"]/vsys/entry[@name="vsys1"]/tag element: |- <entry name="{{ tag_name }}"> <color>{{ tag_color }}</color> <comments>{{ tag_description }}</comments> </entry> - name: object_edl xpath: /config/devices/entry[@name="localhost.localdomain"]/vsys/entry[@name="vsys1"] element: |- <external-list> <entry name="{{ edl_name }}"> <type> <ip> <recurring> <five-minute/> </recurring> <description>{{ edl_desc }}</description> <url>{{ edl_url }}</url> </ip> </type> </entry> </external-list> - name: policy_security_outbound xpath: /config/devices/entry[@name="localhost.localdomain"]/vsys/entry[@name="vsys1"]/rulebase/security/rules element: |- <entry name="{{ edl_name }}-out"> <to> <member>any</member> </to> <from> <member>any</member> </from> <source> <member>any</member> </source> <destination> <member>{{ edl_name }}</member> </destination> <source-user> <member>any</member> </source-user> <category> <member>any</member> </category> <application> <member>any</member> </application> <service> <member>application-default</member> </service> <hip-profiles> <member>any</member> </hip-profiles> <tag> <member>{{ tag_name }}</member> </tag> <action>deny</action> <description>outbound EDL IP block rule. EDL info: {{ edl_desc }}</description> </entry> - name: security_policy_inbound xpath: /config/devices/entry[@name="localhost.localdomain"]/vsys/entry[@name="vsys1"]/rulebase/security/rules element: |- <entry name="{{ edl_name }}-in"> <to> <member>any</member> </to> <from> <member>any</member> </from> <source> <member>{{ edl_name }}</member> </source> <destination> <member>any</member> </destination> <source-user> <member>any</member> </source-user> <category> <member>any</member> </category> <application> <member>any</member> </application> <service> <member>application-default</member> </service> <hip-profiles> <member>any</member> </hip-profiles> <tag> <member>{{ tag_name }}</member> </tag> <action>deny</action> <description>inbound EDL IP block rule. EDL info: {{ edl_desc }}</description> </entry>
validate_xml_edl_policy.skillet.yaml
file with the validation sub-skillet contentsShow/Hide the validation skillet contents
# skillet preamble information used by panhandler # --------------------------------------------------------------------- # unique snippet name name: validate_xml_edl_policy # label used for menu selection label: Sample SkilletBuilder validation for EDL, tag, and security policy description: | Used by SkilletBuilder to demonstrate configuration capturing and validation skillet creation. # type of device configuration # common types are panorama, panos, and template # https://github.com/PaloAltoNetworks/panhandler/blob/develop/docs/metadata_configuration.rst type: pan_validation # grouping of like snippets for dynamic menu creation in panhandler labels: collection: - Skillet Builder # --------------------------------------------------------------------- # end of preamble section # variables section # --------------------------------------------------------------------- # variables used in the configuration templates # type_hint defines the form field used by panhandler # type_hints examples include text, ip_address, or dropdown variables: # variables used for connection with NGFW; type_hint of hidden since # the values are cached in the context after the workflow skillet - name: TARGET_IP description: NGFW IP or Hostname default: 192.168.55.10 type_hint: hidden - name: TARGET_USERNAME description: NGFW Username default: admin type_hint: hidden - name: TARGET_PASSWORD description: NGFW Password default: admin type_hint: hidden - name: edl_url description: External Dynamic List URL default: http://someurl.com type_hint: hidden # --------------------------------------------------------------------- # end of variables section # snippets section # --------------------------------------------------------------------- snippets: # Capture the name of the IP External Dynamic Lists with URL set to user-inputted edl_url - name: capture_external_lists cmd: parse variable: config outputs: - name: external_lists capture_object: /config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']/external-list - name: user_edl_name capture_value: /config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']/external-list/entry/type/ip/url[text()="{{ edl_url }}"]/../../../@name # Verify that the captured name isn't null, meaning an EDL is configured - name: test_external_lists label: configure IP External Dynamic List (EDL) object test: | ( user_edl_name | length ) fail_message: | There are no External Dynamic Lists (EDL) configured on this firewall for {{ edl_url }}. pass_message: | The External Dynamic List (EDL), {{ user_edl_name }}, is configured for {{ edl_url }}. documentation_link: https://docs.paloaltonetworks.com/pan-os/10-0/pan-os-web-interface-help/objects/objects-external-dynamic-lists # Capture the name of security rules that deny from source/destination EDL - name: capture_security_rules cmd: parse variable: config outputs: - name: security_rules_with_EDL_source capture_list: /config/devices/entry/vsys/entry/rulebase/security/rules/entry[source/member/text()="{{ user_edl_name }}"][action/text()="deny"]/@name - name: security_rules_with_EDL_destination capture_list: /config/devices/entry/vsys/entry/rulebase/security/rules/entry[destination/member/text()="{{ user_edl_name }}"][action/text()="deny"]/@name # Verify that the captured list isn't null, meaning security rules are configured - name: test_security_rules_out label: configure security rule blocking traffic to EDL object test: | ( security_rules_with_EDL_destination | length ) fail_message: | There are no security rules denying traffic to the destination of External Dynamic Lists (EDL) object. pass_message: At least one security rule with EDL destination is configured. documentation_link: https://docs.paloaltonetworks.com/pan-os/10-0/pan-os-admin/policy/use-an-external-dynamic-list-in-policy/enforce-policy-on-an-external-dynamic-list.html - name: test_security_rules_in label: configure security rule blocking traffic from EDL oject test: | ( security_rules_with_EDL_source | length ) fail_message: | There are no security rules denying traffic from the source of External Dynamic Lists (EDL) object. pass_message: At least one security rule with EDL source is configured. documentation_link: https://docs.paloaltonetworks.com/pan-os/10-0/pan-os-admin/policy/use-an-external-dynamic-list-in-policy/enforce-policy-on-an-external-dynamic-list.html # --------------------------------------------------------------------- # end of snippets section
template_xml_edl_policy.skillet.yaml
file with the template sub-skillet contentsShow/Hide the template skillet contents
# skillet preamble information used by panhandler # --------------------------------------------------------------------- # unique snippet name name: template_xml_edl_policy # label used for menu selection label: Sample template skillet used for workflow tutorial description: Used by SkilletBuilder to demonstrate workflow completion output messaging. # type of device configuration # common types are panorama, panos, and template # https://github.com/PaloAltoNetworks/panhandler/blob/develop/docs/metadata_configuration.rst type: template # grouping of like snippets for dynamic menu creation in panhandler labels: collection: - Skillet Builder # --------------------------------------------------------------------- # end of preamble section # variables section # --------------------------------------------------------------------- # variables used in the configuration templates # type_hint defines the form field used by panhandler # type_hints examples include text, ip_address, or dropdown variables: # type_hint of hidden since the values are cached in the context # after the workflow skillet - name: TARGET_IP description: NGFW IP or Hostname default: 192.168.55.10 type_hint: hidden - name: edl_name description: name of the external list default: my_edl type_hint: hidden - name: tag_name description: tag name default: tag name type_hint: hidden # --------------------------------------------------------------------- # end of variables section # snippets section # --------------------------------------------------------------------- snippets: # contextual name with the name of the template file - name: output_message file: template_output_report.j2 # --------------------------------------------------------------------- # end of snippets section
template_output_report.j2
file with the template HTML output contentsShow/Hide the template HTML output contents
<div> <br/> <h2 style="text-align:center;">WORKFLOW COMPLETED</h2> <br/> The External Dynamic List, named <i>{{ edl_name }}</i>, was added to the configuration of the NGFW ({{ TARGET_IP }}). In addition, security policies with the tag <i>{{ tag_name }}</i> were configured to deny traffic to and from this EDL. <br/> <br/> For a step-by-step tutorial on building workflows, please navigate to the <a href="">Workflow Tutorial</a> in the SkilletBuilder documentation. </div>The directory structure will look like:
Create the Workflow Skillet Skeleton¶
In your PanHandler Web GUI, navigate to PanHandler dropdown menu in the top left of the page, and click on Skillet Collections.
Scroll down the Skillet Collections page until you find the Skillet Builder tile, and click Go.
Scroll down the Skillet Builder Collections page until you find the Skillet YAML File Template tile, and click Go.
The Skillet YAML File Template provides an easy user interface for building the skillet structure and populating the Preamble Attributes.
Here are the suggested tutorial inputs:
Skillet ID: workflow_xml_edl_policy
Skillet Label: Sample SkilletBuilder workflow for EDL validation and configuration
Skillet Description: Used by SkilletBuilder to demonstrate chaining skillets together as workflow solutions.
Collection Name: Skillet Builder
Skillet Type:
workflow
Click Submit to view the rendered template. This YAML file template contains:
Preamble populated with the web form values
Variables section with placeholder values
Snippets section with placeholder values
Copy this template and paste it into the
workflow_tutorial.skillet.yaml
file in your repository’sedl_xml_policy_workflow
folder. Since the variables and snippets sections are populated with filler, you can delete these sections to get the main workflow skillet’s skeleton.Show/Hide the workflow skillet skeleton
# skillet preamble information used by panhandler # --------------------------------------------------------------------- # unique snippet name name: workflow_xml_edl_policy # label used for menu selection label: Sample SkilletBuilder workflow for EDL validation and configuration description: Used by SkilletBuilder to demonstrate chaining skillets together as workflow solutions. # type of device configuration # common types are panorama, panos, and template # https://github.com/PaloAltoNetworks/panhandler/blob/develop/docs/metadata_configuration.rst type: workflow # grouping of like snippets for dynamic menu creation in panhandler labels: collection: - Skillet Builder # --------------------------------------------------------------------- # end of preamble section # variables section # --------------------------------------------------------------------- # variables used in the configuration templates # type_hint defines the form field used by panhandler # type_hints examples include text, ip_address, or dropdown variables: # --------------------------------------------------------------------- # end of variables section # snippets section # --------------------------------------------------------------------- snippets: # --------------------------------------------------------------------- # end of snippets section
Add Variables to the Skillet¶
Similar to other skillet types, workflow skillets utilize Variables in the variables section to prompt the user for input, which gets checked for proper formatting, and to vary the automation workflow (in an expected way) to handle many use cases.
In workflow skillets, users can be prompted for input in two main situations:
On the beginning workflow menu, defined in the main workflow’s YAML file
In the middle of the workflow, defined in a sub-skillet’s YAML file
Variables defined in the main workflow YAML file are saved to context and can be used by all of the following sub-skillets. This option is useful for variables that are already known to the user going into the automation and that do not depend on a sub-skillet’s execution. For example, each sub-skillet in the tutorial workflow needs to know the firewall’s access credentials, which will not change during the workflow execution, so defining the IP, username, and password in the main workflow menu minimizes and streamlines user input.
Add the following YAML code to the variables section of the
workflow_tutorial.skillet.yaml
file:# variables section # --------------------------------------------------------------------- # variables used in the configuration templates # type_hint defines the form field used by panhandler # type_hints examples include text, ip_address, or dropdown variables: - name: TARGET_IP description: NGFW IP or Hostname default: 192.168.55.10 type_hint: fqdn_or_ip - name: TARGET_USERNAME description: NGFW Username default: admin type_hint: text - name: TARGET_PASSWORD description: NGFW Password default: admin type_hint: password - name: edl_url description: External Dynamic List's Source URL default: http://someurl.com type_hint: text - name: assess_options description: Config Validation Options default: [] type_hint: checkbox cbx_list: - key: Validate configuration at the beginning of the workflow value: run_validation_begin - key: Validate configuration at the end of the workflow value: run_validation_end # --------------------------------------------------------------------- # end of variables sectionNote
When you move variables to the front of the workflow, you MUST still include the necessary variables in each individual sub-skillet.
A sub-skillet will only ever see the variables defined in its variables list, even if that variable is loaded into the context.
In order to minimize the amount of user interaction, you will need to change the variables’ type_hint in each sub-skillet’s variables section to hidden. This will load the variable from context for the sub-skillet to use and will not prompt a user to re-define it.
The validation skillet’s variables section is then changed to:
variables: # variables used for connection with NGFW; type_hint of hidden since # the values are cached in the context after the workflow skillet - name: TARGET_IP description: NGFW IP or Hostname default: 192.168.55.10 type_hint: hidden - name: TARGET_USERNAME description: NGFW Username default: admin type_hint: hidden - name: TARGET_PASSWORD description: NGFW Password default: admin type_hint: hidden - name: edl_url description: External Dynamic List URL default: http://someurl.com type_hint: hidden # --------------------------------------------------------------------- # end of variables sectionTip
YAML is notoriously finicky about whitespace and formatting. While it’s a relatively simple structure and easy to learn, it can often also be frustrating to work with. A good reference to use to check your YAML syntax is the YAML Lint site.
A common problem with developing workflow skillets is variable name matching across all the skillets. You must make sure that a variable’s name matches from skillet to skillet. If they do not match and you don’t have the ability to change the names (This could happen if you don’t own the sub-skillets), you can use a transform attribute in the snippets section to map one sub-skillet’s output variable to another sub-skillet’s input variable. For examples of this attribute in a workflow, navigate to the SkilletLib repo in GitHub.
Add Snippets to the Skillet¶
For main workflow skillets, each snippet in the snippets section is the name of a skillet to be executed in turn. You can find the unique name of each sub-skillet by opening the sub-skillet’s YAML file and locating the name attribute in the preamble section. Each of the sub-skillet’s names have to be globally unique for the main workflow skillet to understand which sub-skillet to execute.
Conditional execution of a sub-skillet is accomplished by using the when attribute underneath the sub-skillet’s name in question. That snippet will only run when the conditional logic defined with the when attribute evaluates as True.
For this tutorial, if the user decides to validate at both the beginning and end of the workflow, the sequence of execution is validate, config, validate, and then output a message. As seen in the main workflow skillet’s snippet section below, this sequence was achieved by the intentional ordering of snippet names.
In order to take the user’s input into account regarding the validation ordering, when attributes are placed after each validation snippet and defined with the logical statement of
"'run_validation_begin' in assess_options"
. This evaluates to when theassess_options
checkbox’s list item with the valuerun_validation_beginning
is checked, run the snippet.Add the following YAML code to the snippets section of the
workflow_tutorial.skillet.yaml
file:# snippets section # --------------------------------------------------------------------- snippets: # Run the validation skillet if the user checks the checkbox - name: validate_xml_edl_policy when: "'run_validation_begin' in assess_options" - name: config_xml_edl_policy # Run the validation skillet if the user checks the checkbox - name: validate_xml_edl_policy when: "'run_validation_end' in assess_options" # Finish with output message of completion to the user - name: template_xml_edl_policy # --------------------------------------------------------------------- # end of snippets sectionIn addition to when attributes, the only other attribute used in the snippet section of workflow skillets is transform. You may optionally also include a transform attribute, which will map the output from one sub-skillet to the input of another. For an example of a workflow skillet using transform, navigate to the SkilletLib repo in GitHub.
Warning
To avoid PanHandler skillet import errors, skillets’ names must be globally unique.
Push the Skillet to GitHub¶
At this stage initial building is complete. The YAML file preamble, variables, and snippets sections all have relevant content added. Now we want to push this to GitHub for additional testing and tuning.
Use:
git add .
to add the modified files to the commit
git commit -m "message"
to commit the files with a change message
git push origin master
to push to the repo master branch
Test and Troubleshoot¶
Now that the skillet has been pushed to GitHub, the skillet can be imported or loaded into one of the skillet player tools, such as PanHandler or SLI, for testing. Similar to designing, testing involves three main components:
User-facing menu options
Overall sequence of sub-skillets
Conditional execution of each sub-skillet
Continue reading to see how to test these components in various skillet players.
Test the Skillet in PanHandler¶
Import the workflow skillet into PanHandler (instructions and troubleshooting found here), and open the Sample SkilletBuilder workflow for EDL validation and configuration workflow skillet from either the Skillet Collections or Skillet Repositories page.
From this page, make sure that all of the workflow menu options appear as expected. For advanced variable types and attributes, such as toggle_hint, test all variations of the workflow menu and verify that these variables appear appropriately. If they do not appear as expected, you must go back into the variables section of the workflow skillet and troubleshoot.
The main workflow skillet itself does not have a Debug tool like the other skillet types, so you will need to manually verify that the overall sub-skillet sequence is correct by stepping through the workflow. However, configuration sub-skillets do allow for inline debugging with the Skillet Debugger. Continue by clicking Submit.
You should now be prompted with the user-input section of the first sub-skillet, the validation skillet. All of this sub-skillet’s variables (firewall IP, username, and password and EDL URL) will not appear on the screen since they are defined as type hidden. Do know that every variable defined in the variable section of the sub-skillet’s YAML file will get loaded from the context. Use this guide to view all variables and their values in the context.
Continuing the workflow, you should see the validation output. Verify this output using the validation skillet testing found in the Validation Tutorial. In addition, you can find more resources in the PanHandler documentation for Creating and Debugging Validation Skillets.
Click Continue, and then fill out the forms for the Sample SkilletBuilder skillet with EDL, tag, and security policy configuration skillet. Click Continue to land on the Configure Target Information screen.
On the target screen, you can click Debug, which gives an inline overview of each snippet in the configuration skillet. You can check here to make sure your user inputs are correct and the XML is formatted properly.
Finish out the workflow by continuing through each of the steps until you land on the Workflow Completed page, which is rendered from the final template sub-skillet.
If a sub-skillet gets skipped or runs when it’s not intended to, check the Context for variable names and values currently cached. In addition, you can troubleshoot by viewing the context both before and after a sub-skillet is run to see which variables get generated during the sub-skillet execution.
Note
If your workflow has a sequence of configure then validate, you will need to commit after the configuration skillet to see any changes in the validation skillet since the validation only looks at the running configuration.
Test the Skillet with SLI¶
The SLI python package provides users a command line interface (CLI) for interacting with skillets, including a workflow skillet. Testing with SLI is very similar to Test the Skillet in PanHandler, except that you are using the command line instead of a Web GUI.
After installing SLI in a python virtual environment and cloning your GitHub repository onto your local machine, change directory into the root of your repository. This SLI tutorial is going to use the SkilletBuilder’s repository as an example of your GitHub repo.
Start by loading and viewing all skillets inside your repository, using
sli load
.(venv) testing-device:~/SkilletBuilder$ sli load Name Type ----------------------------------------------------------- preview_xml_changes workflow generate_skillet_preview_output template generate_skillet_preview_online python3 generate_skillet_preview_offline python3 SkilletBuilderSample_EDL_policy panos configuration_explorer python3 template_xml_edl_policy template validate_xml_edl_policy pan_validation workflow_xml_edl_policy workflow config_xml_edl_policy panos skeleton_yaml_file_xml template generate_set_cli workflow generate_set_cli_online python3 generate_set_cli_offline python3 generate_ansible_playbook python3 test_skillet_inline python3 sample_validation_skilletbuilder pan_validation skeleton_yaml_file_any template Generate_Skillet_workflow_exp workflow generate_skillet_snippets_from_device python3 generate_skillet_snippets_from_config python3 (venv) testing-device:~/SkilletBuilder$Next, run the workflow skillet using the context with
sli workflow -uc --name workflow_xml_edl_policy
, where workflow_xml_edl_policy is the name of your workflow skillet as seen in the load command. This executes the main workflow skillet and starts the user-interaction piece of the skillet menu.(venv) testing-device:~/SkilletBuilder$ sli workflow -uc --name workflow_xml_edl_policy Device: 192.168.1.1 Username: admin Password: NGFW IP or Hostname (192.168.1.1): NGFW Username (admin): NGFW Password: Confirm - NGFW Password: External Dynamic List's Source URL (http://someurl.com): http://sampleurl.com Config Validation Options ------------------------- Validate configuration at the beginning of the workflow (y/n default: no): y Validate configuration at the end of the workflow (y/n default: no): y Input Value -------------------------------------------------------------------- Validate configuration at the beginning of the workflow Yes Validate configuration at the end of the workflow Yes Are These answers ok? (y/n): y End of user variables. Running skillet validate_xml_edl_policy - pan_validation . . .Note
Variables in the user-input stage are either defined as a user’s input to the command line or as the value currently stored in the context (as seen next to a variable in parenthesis). To use the context’s value, the user MUST only input enter.
To view the values stored in the context, use the command
sli show_context -nc
. The-nc
flag removes the entire configuration XML from the output for easier viewing.For additional information about SLI, view the documentation on the SLI PyPI page.
Document¶
The final stage is to document key details about the skillet to provide contextual information to the user community.
README.md¶
The workflow skillet repository has an empty placeholder
README.md
that should give an overview of the solution. TheREADME.md
should provide skillet-specific details such as what the skillet does, variable input descriptions, and caveats and requirements.
README.md
uses the markdown formatting language. Numerous examples can be found in the skillet files. There is also a wide array of markdown cheat sheets you can find using Google searches. Below are a few common markdown elements you can use in your documentation. Most IDEs can display the user view as you edit the markdown file.
Markdown syntax options
#, ##, ### for header text levels (H1, H2, H3, etc.)
**text** for bold text
*text* or _text_ to underline
1. text to create numbered lists
* text, + text, - text for bullet style lists
[text](url) for inline web links
`test` to highlight a text string
```text block - one or more lines``` to create a highlighted text block
Tip
To view markdown edits for existing GitHub repos, click on the README.md file, then use the Raw option to display the output as raw markdown text. From here, you can copy and paste or review formatting.
Paste this sample
README.md
file into your repository and push to GitHub.# Sample Workflow Skillet This is used in the training material as part of the Workflow tutorial. The solution utilizes three skillets: 1. A validation skillet to verify the running configuration 2. A configuration skillet to configure: * tag: create a tag using inputs for name, description, and color * external-list: create an edl using inputs for name, description, and url * security policies: inbound and outbound security policies referencing the edl and tag names 3. A template skillet to output the workflow end The configuration skillet was taken from the Configuration Tutorial for Skillet Builder documentation (https://skilletbuilder.readthedocs.io/en/latest/tutorials/tutorial_configuration.html#). ## Workflow Sequence This workflow skillet begins by prompting the user to input the workflow menu options, described below. Depending on the *assess_options* result, a validation skillet will be run next to verify that an External Dynamic List object is configured for the *edl_url* inputted by the user. In addition, it will validate that two security policies exist denying traffic from and to the EDL object. Next, the workflow prompts the user to fill in forms about the EDL and tag information. With this information, the automation pushes a configuration that creates a tag object, EDL object, and two security policies. Again, depending on the *assess_options* result, the same validation skillet will be run. Finally, a template skillet is executed that outputs a **Workflow Completed** message, so the user is clear about the workflow's end. ## Variables ### Main Workflow Menu Options: * *TARGET_IP*: IP of firewall to validate and configure * *TARGET_USERNAME*: Username of firewall management user * *TARGET_PASSWORD*: Password of the above user * *edl_url*: URL used for the External Dynamic List * *assess_options*: Checkbox for validation skillet execution orders (beginning and/or end of the workflow) ### Configuration Sub-Skillet Options: * *tag_name*: Name of a newly created tag that is used in the security rules * *tag_description*: Text field to describe the tag * *tag_color*: Dropdown menu mapping color names to color numbers (required in the XML configuration) * *edl_name*: Name of the newly created External Dynamic List * *edl_description*: Text field used to describe the External Dynamic List The 'recurring' value for the EDL is set to *five-minutes*. This could be added as a variable but for this example, the value is considered a recommended practice so not configurable in the skillet. The EDL type is set to IP since used in the security policy and is not configurable in the skillet. ### Configuration Sub-Skillet Security Policy Referencing Variables The security policy does not have its own variables asking for rule name, zones, or actions. The rules are hardcoded with 'any' for most attributes and action as _deny_ to block traffic matching the EDL IP list. The security rule names use the EDL name followed by '-in' and '-out' to create unique security policies for each EDL. This is denoted in the yaml file with ```{{ edl_name }}``` included in the rule name.Support Policy Text
Skillets are not part of Palo Alto Networks supported product so the policy text is appended to the README file to specify skillets are not supported. Sample text to copy/paste is found in the SkilletBuilder repo README
LIVEcommunity¶
Skillets can be shared in the LIVEcommunity as Community or Personal skillets. Community Skillets are expected to have a higher quality of testing, documentation, and ongoing support. Personal skillets can be shared as-is to create awareness and eventually become upgraded as Community Skillets.
Playlist Includes¶
Overview¶
This tutorial is geared toward skillet developers that want to be able to reuse pieces of a skillet multiple times. This can be done by creating a playlist with skillet includes. Skillet includes allow snippets from other skillets to be referenced and included in the playlist. This solution is recommended for the following use cases:
Creating several skillets that are similar, but have minor content differences
Recreating skillets that already exist, but with minor tweaks
Keeping a skillet up to date with latest content releases
Pulling together content from multiple skillets
This Playlist Includes tutorial highlights the first three use cases. The focus of this tutorial will show how the playlist model for IronSkillet works. This uses a submodule called ironskillet-components that is used to build several playlists that contain different content groups of the IronSkillet configuration. By using a submodule, it is easy to update the sub-skillets in one place, and have the playlists pull the latest snippets available. To make terms more clear in this tutorial, the skillets that the playlist include snippets come from will be called sub-skillets. This means we will be constructing a playlists containing snippets from sub-skillets.
The final repository built from this tutorial can be viewed here.
- Click below to jump to a specific section of the tutorial:
Prerequisites¶
Before moving forward with the tutorial, you will need the following:
Create a GitHub repository, instructions here
Open your repository in a text editor or IDE
Install or Update PanHandler using Docker, instructions here
Deploy a Next Generation Firewall and Panorama for testing with proper access to GUI and CLI (via SSH)
Set Up the Submodule¶
In this tutorial we are using ironskillet-components, which contains all of the sub-skillets for IronSkillet 9.1+. Each folder for the PAN-OS version has all of the
panos
andpanorama
sub-skillets, which are broken down into separate files by the XPath that the snippets use to push the configuration. This was done to keep like snippets together, as well as group configurations by categories that could be included or excluded based on the preference of the user. A breakdown of which sub-skillets correspond to which configuration elements can be found in the IronSkillet documentation. This tutorial uses the 10.0panos
andpanorama
sub-skillets to build several configuration playlists.
Add the Submodule¶
To add this submodule to the new repository that will contain the playlists, use the following steps:
Open the new repository in your IDE or text editor of choice
Create a new folder in the root directory called Submodules
Navigate into that folder
Run
git submodule add https://github.com/PaloAltoNetworks/ironskillet-components.git
This will add a copy of the files at that specific commit to your working directory. A
.gitmodules
file will automatically appear, containing the information about the submodule just added. The sub-skillets from the submodule are now able to be included in a playlist. See the Git Submodules Overview for more information on submodules and how they work.The working tree of the repository (green square) and
.gitmodules
file (red square) should look like the following after following the steps above:If you commit and push these changes to your repository, the submodules directory should look like the following:
The final repository built from this tutorial can be viewed here. If trying to explore the submodule in the tutorial example after cloning, the submodule will need to be initiated and updated beforehand. To do this, follow these steps.
Clone the repository
Open the repository (in an IDE, or
cd
in a terminal)Run
git submodule init
Run
git submodule update
This will use the
.gitmodules
file to initiate the ironskillet-components repo in the submodules folder, and then pull down the latest commit.
Sub-Skillets in Submodule¶
Looking at ironskillet-components, there are a few best practices for sub-skillets to note. The first is the structure of the repository, with all sub-skillets easily grouped by PAN-OS version, and then by type (panos or panorama). This allows for easy tracking of all the sub-skillets and simple referencing later on. Second is the naming scheme, with all file names following
file_name.skillet.yaml
. The.skillet.yaml
file ending is important to identify that this is a sub-skillet that could be included in another playlist. It is recommended to keep the file names short, descriptive, and unique, as the name of the sub-skillet must be specified in a playlist include.Inside each sub-skillet, the meta-data preamble structure is the same as any normal skillet would have. To keep naming conventions simple, it is recommended to use the same
file_name
specified as the external file name as the internal skillet name in the header. Another handy attribute to include is the collection a sub-skillet should be included in. This is because it is possible to load repositories with many sub-skillets into PanHandler, and it makes it much easier to find the sub-skillet you are looking for if they are sorted into descriptive collections. See Metadata Attributes page for more information on Preamble Attributes and further options to specify. The preamble for panos_ngfw_device_setting_10_0.skillet.yaml is included below to illustrate the practices mentioned above.name: panos_ngfw_device_setting_10_0 label: PAN_OS NGFW Device - Setting description: |- reference device setting configuration snippets type: panos labels: collection: - IronSkillet 10.0 PAN-OS SnippetsWhen ironskillet-components is imported into PanHandler (as of the writing of this tutorial), the following collections are available. Each of the sub-skillets within these collections loaded can be run individually.
Another best practice to mention is that each sub-skillet should include all information needed to configure all snippets by itself. This means that any variables used or XML included in the snippet must be included directly in the sub-skillet. This allows each sub-skillet to be run and debugged individually, and ensures that the playlist that includes the sub-skillet will be able to find all the information needed to run the snippet. Also, it is not possible to include a skillet include, which is why any XML must be directly specified within the snippets of a sub-skillet.
The final recommendation for sub-skillets pertains to the individual snippets within the sub-skillet. Each of the snippets in a sub-skillet should include a piece of XML small enough to encompass one action. For example, each of the IronSkillet antivirus security profiles are broken down into their own snippets. For the five profiles (Alert-Only, Inbound, Outbound, Internal, and Exception), there exists a snippet that can then be included or not included in a playlist. This subsetting of information is important to provide granularity in choosing what can be included or excluded from a playlist down the road. See the panos_ngfw_profile_antivirus_10_0.skillet.yaml for more in depth information.
Warning
All snippets and sub-skillets within a submodule repository must have unique names. This is required for referencing later in playlist includes.
Build the Playlist¶
A playlist is nearly identical to any other skillet, with the main difference being the variable and snippet includes. This means that the format and headers will be the same as a normal skillet. The following section will walk through how to build out a playlist, and show examples of how to include snippets from a sub-skillet in various ways.
Set Up the Directory Structure¶
Open the new repository in your IDE or text editor of choice
Create a new folder in the root directory called Playlists
Navigate into that folder
Create three new files with the following names
ironskillet_panos_full_10_0.skillet.yaml
ironskillet_panos_alert_only_10_0.skillet.yaml
ironskillet_panorama_notshared_security_policies_10_0.skillet.yaml
Playlist file names should follow the pattern
playlist_name.skillet.yaml
. This allows the skillet players (PanHandler, SLI) to recognize that it is a playlist and load the snippets accordingly. In this tutorial, playlist names will mention IronSkillet, the device type to be configured (panos
orpanorama
), type of playlist, and the PAN-OS version. This gives an accurate description of what is included in the playlist without having to open it and try to decipher the skillet includes. See below for what the directory should look like after following these steps.
Playlist Preamble¶
Each playlist should have a preamble, just like any skillet or sub-skillet. Since there a lot of sub-skillets, snippets, and playlists to keep track of with this model and with this tutorial, it is recommended to keep a consistent naming scheme. With the sub-skillet names following
file_name.skillet.yaml
, it is highly recommended to use thefile_name
portion as the internal skillet or playlist name.For example, the playlist file
ironskillet_panos_full_10_0.skillet.yaml
would have an internal name ofironskillet_full_10_0
. Similarly, one of the sub-skillets namedpanos_ngfw_device_system_10_0.skillet.yaml
would have an internal skillet name ofpanos_ngfw_device_system_10_0
. This makes it easy to know how to reference the sub_skillets in the playlist using skillet includes.Specifying the label, description, type, and collection are also highly recommended, as they allow for easier viewing of the playlists once loaded into PanHandler, and is generally good practice for documentation. In particular, the type is very important, as that tells the skillet player of your choice what type of snippets will be included in a configuration.
The playlist preambles should look like the following:
PAN-OS Full Skeleton
name: ironskillet_panos_full_10_0 label: IronSkillet PAN-OS 10.0 description: |- group of snippets for ironskillet 10.0 type: panos labels: collection: - IronSkillet Playlists variables: snippets:PAN-OS Alert Only Skeleton
name: ironskillet_panos_alert_only_10_0 label: IronSkillet Alert-Only 10.0 description: |- group of alert only policies for ironskillet 10.0 type: panos labels: collection: - IronSkillet Playlists variables: snippets:Panorama Not-Shared Security Policies Skeleton
name: ironskillet_panorama_notshared_security_policies_10_0 label: IronSkillet Panorama Not-Shared Security Policies 10.0 description: |- group of security policies for panorama not-shared ironskillet 10.0 type: panorama labels: collection: - IronSkillet Playlists variables: snippets:See Metadata Attributes page for more information on Preamble Attributes and further options to specify. The
variables:
andsnippets:
sections are blank for now, but will be added to in the following sections.Tip
YAML is notoriously finicky about whitespace and formatting. While it’s a relatively simple structure and easy to learn, it can often also be frustrating to work with. A good reference to use to check your YAML syntax is the YAML Lint site.
Including Snippets¶
There are different use cases for include snippets from sub-skillets in a playlist. The main ways are listed below, and will be highlighted when building out the playlists in the following section:
Load entire sub-skillet as is
Load only certain snippets from a sub-skillet
Load and change the element of snippets in a sub-skillet
Load and change XPath of snippets in a sub-skillet (particularly useful with different panorama setups)
Case 1: Load entire sub-skillet as is
To include an entire sub-skillet into a playlist, in the snippet section of the playlist, create entries that have a name and include set to the internal sub-skillet name defined in the preamble of the sub-skillet. In this example, all of the snippets from the Device System sub-skillet will be included in the playlist. Any variables in the sub-skillet are included by default.
snippets: - name: panos_ngfw_device_system_10_0 include: panos_ngfw_device_system_10_0
Case 2: Load only certain snippets from a sub-skillet
If only certain snippets within a sub-skillet should be included in a playlist, still specify the name and include of the entry in the snippet section of the playlist like the above example. Then, add an include_snippets attribute and list out each name of the snippets from the sub-skillet to be included. In this example, only the Alert Only Antivirus security profile is included from the Antivirus sub-skillet.
snippets: - name: panos_ngfw_profile_antivirus_10_1 include: panos_ngfw_profile_antivirus_10_1 include_snippets: - name: ironskillet_antivirus_alert_allNote
Any variables in the sub-skillet must be specifically included when choosing a subset of snippets to include. This is covered in the Including Variables section of this tutorial.
Case 3: Change the element of a snippet in a sub-skillet
Sometimes, there may be one snippet in a sub-skillet that has XML changes needed for a playlist that will overwrite the sub-skillet snippet. This can easily be done through overwriting the element attribute of the snippet from the sub-skillet. In this example the login banner snippet was changed from the default in the Device System sub-skillet, but the other five snippets were kept as is.
snippets: - name: panos_ngfw_device_system_10_0 include: panos_ngfw_device_system_10_0 include_variables: all include_snippets: - name: ironskillet_device_system_dynamic_updates - name: ironskillet_device_system_snmp - name: ironskillet_device_system_ntp - name: ironskillet_device_system_timezone - name: ironskillet_device_system_hostname - name: ironskillet_device_system_login_banner element: |- <login-banner>You have accessed a protected system. If not authorized, log off immediately.</login-banner>Warning
Notice that there is an
include_variables: all
attribute before theinclude_snippets:
. This is because there are variables used in the other snippets that need to be carried over into the playlist. When making overrides to snippets usinginclude_snippets:
, this is a required step.
Case 4: Change XPath of a snippet in a sub-skillet
Similar to the above example, sometimes the XPath of a snippet will need to be changed due to panorama configuration (Shared or Not-Shared). The XPath specifies where in the XML the element should be placed, which can change due to how the device is set up. Panorama in particular often has a different XPath depending if it is a shared or not-shared setup. See IronSkillet Documentation for more information about this. In ironskillet-components, the shared XPath was chosen as the default for the xpath attribute in the panorama sub-skillets. In this example, a Not-Shared playlist is being built, so the XPath will have to be changed to the not-shared version for some sub-skillets. Each snippet in the sub-skillet must be individually included and have the XPath ‘overwritten’, even though the XPath for all snippets in the file might be changing to the same path.
snippets: - name: panorama_tag_10_0 include: panorama_tag_10_0 include_snippets: - name: ironskillet_tag_ironskillet_version xpath: /config/devices/entry[@name='localhost.localdomain']/device-group/entry[@name='{{DEVICE_GROUP}}']/tagNote
Notice that there is a new
DEVICE_GROUP
variable introduced in the changed XPaths. This variable will need to be included in the playlistvariables:
section.
Add Snippets to Playlists
Using the processes explained above, the sub-skillets should now be added to the playlist as follows:
PAN-OS Full Playlist [show/hide snippets]
snippets: # IronSkillet baseline # general device system and setting configs - name: panos_ngfw_device_system_mgmt_ip_10_0 include: panos_ngfw_device_system_mgmt_ip_10_0 - name: panos_ngfw_device_system_dns_10_0 include: panos_ngfw_device_system_dns_10_0 - name: panos_ngfw_device_system_10_0 include: panos_ngfw_device_system_10_0 - name: panos_ngfw_device_setting_10_0 include: panos_ngfw_device_setting_10_0 - name: panos_ngfw_mgt_config_users_10_0 include: panos_ngfw_mgt_config_users_10_0 - name: panos_ngfw_password_complexity_10_0 include: panos_ngfw_password_complexity_10_0 # shared log settings and profile - name: panos_ngfw_shared_log_settings_10_0 include: panos_ngfw_shared_log_settings_10_0 - name: panos_ngfw_shared_log_settings_email_profile_10_0 include: panos_ngfw_shared_log_settings_email_profile_10_0 - name: panos_ngfw_shared_log_settings_email_system_critical_10_0 include: panos_ngfw_shared_log_settings_email_system_critical_10_0 # tag object - name: panos_ngfw_tag_10_0 include: panos_ngfw_tag_10_0 # log settings - name: panos_ngfw_log_settings_profiles_10_0 include: panos_ngfw_log_settings_profiles_10_0 - name: panos_ngfw_log_settings_profiles_email_10_0 include: panos_ngfw_log_settings_profiles_email_10_0 # security profiles and profile groups - name: panos_ngfw_profile_custom_urlFiltering_10_0 include: panos_ngfw_profile_custom_urlFiltering_10_0 - name: panos_ngfw_profile_decryption_10_0 include: panos_ngfw_profile_decryption_10_0 - name: panos_ngfw_profile_antivirus_10_0 include: panos_ngfw_profile_antivirus_10_0 - name: panos_ngfw_profile_spyware_10_0 include: panos_ngfw_profile_spyware_10_0 - name: panos_ngfw_profile_vulnerability_10_0 include: panos_ngfw_profile_vulnerability_10_0 - name: panos_ngfw_profile_file-blocking_10_0 include: panos_ngfw_profile_file-blocking_10_0 - name: panos_ngfw_profile_urlFiltering_10_0 include: panos_ngfw_profile_urlFiltering_10_0 - name: panos_ngfw_profile_wildfire_analysis_10_0 include: panos_ngfw_profile_wildfire_analysis_10_0 - name: panos_ngfw_profile_group_10_0 include: panos_ngfw_profile_group_10_0 # rulebase - name: panos_ngfw_rulebase_default_security_rules_10_0 include: panos_ngfw_rulebase_default_security_rules_10_0 - name: panos_ngfw_rulebase_security_10_0 include: panos_ngfw_rulebase_security_10_0 - name: panos_ngfw_rulebase_decryption_10_0 include: panos_ngfw_rulebase_decryption_10_0 - name: panos_ngfw_zone_protection_10_0 include: panos_ngfw_zone_protection_10_0 # reports and email - name: panos_ngfw_reports_simple_10_0 include: panos_ngfw_reports_simple_10_0 - name: panos_ngfw_report_group_simple_10_0 include: panos_ngfw_report_group_simple_10_0 - name: panos_ngfw_email_scheduler_10_0 include: panos_ngfw_email_scheduler_10_0PAN-OS Alert Only Playlist [show/hide snippets]
snippets: # tag object - name: panos_ngfw_tag_10_0 include: panos_ngfw_tag_10_0 include_snippets: - name: ironskillet_tag_ironskillet_version # security profiles and profile groups - name: panos_ngfw_profile_custom_urlFiltering_10_0 include: panos_ngfw_profile_custom_urlFiltering_10_0 include_snippets: - name: ironskillet_custom_url_category_allow - name: panos_ngfw_profile_decryption_10_0 include: panos_ngfw_profile_decryption_10_0 - name: panos_ngfw_profile_antivirus_10_0 include: panos_ngfw_profile_antivirus_10_0 include_snippets: - name: ironskillet_antivirus_alert_all - name: panos_ngfw_profile_spyware_10_0 include: panos_ngfw_profile_spyware_10_0 include_variables: all include_snippets: - name: ironskillet_spyware_alert_all - name: panos_ngfw_profile_vulnerability_10_0 include: panos_ngfw_profile_vulnerability_10_0 include_snippets: - name: ironskillet_vulnerability_alert_all - name: panos_ngfw_profile_file-blocking_10_0 include: panos_ngfw_profile_file-blocking_10_0 include_snippets: - name: ironskillet_file_blocking_alert_all - name: panos_ngfw_profile_urlFiltering_10_0 include: panos_ngfw_profile_urlFiltering_10_0 include_snippets: - name: ironskillet_url_alert_all - name: panos_ngfw_profile_wildfire_analysis_10_0 include: panos_ngfw_profile_wildfire_analysis_10_0 include_snippets: - name: ironskillet_wildfire_alert_all - name: panos_ngfw_profile_group_10_0 include: panos_ngfw_profile_group_10_0 include_snippets: - name: ironskillet_profile_group_alert_allPanorama Not-Shared Security Policies Playlist [show/hide snippets]
snippets: # tag object - name: panorama_tag_10_0 include: panorama_tag_10_0 include_snippets: - name: ironskillet_tag_ironskillet_version xpath: /config/devices/entry[@name='localhost.localdomain']/device-group/entry[@name='{{DEVICE_GROUP}}']/tag # security profiles - name: panorama_profiles_custom_url_category_10_0 include: panorama_profiles_custom_url_category_10_0 include_snippets: - name: ironskillet_custom_url_category_block xpath: /config/devices/entry[@name='localhost.localdomain']/device-group/entry[@name='{{DEVICE_GROUP}}']/profiles/custom-url-category - name: ironskillet_custom_url_category_allow xpath: /config/devices/entry[@name='localhost.localdomain']/device-group/entry[@name='{{DEVICE_GROUP}}']/profiles/custom-url-category - name: ironskillet_custom_url_category_no_decrypt xpath: /config/devices/entry[@name='localhost.localdomain']/device-group/entry[@name='{{DEVICE_GROUP}}']/profiles/custom-url-category - name: panorama_profiles_decryption_10_0 include: panorama_profiles_decryption_10_0 include_snippets: - name: ironskillet_decryption_profile xpath: /config/devices/entry[@name='localhost.localdomain']/device-group/entry[@name='{{DEVICE_GROUP}}']/profiles/decryption - name: panorama_profiles_virus_10_0 include: panorama_profiles_virus_10_0 include_snippets: - name: ironskillet_antivirus_alert_all xpath: /config/devices/entry[@name='localhost.localdomain']/device-group/entry[@name='{{DEVICE_GROUP}}']/profiles/virus - name: ironskillet_antivirus_outbound xpath: /config/devices/entry[@name='localhost.localdomain']/device-group/entry[@name='{{DEVICE_GROUP}}']/profiles/virus - name: ironskillet_antivirus_inbound xpath: /config/devices/entry[@name='localhost.localdomain']/device-group/entry[@name='{{DEVICE_GROUP}}']/profiles/virus - name: ironskillet_antivirus_internal xpath: /config/devices/entry[@name='localhost.localdomain']/device-group/entry[@name='{{DEVICE_GROUP}}']/profiles/virus - name: ironskillet_antivirus_exception xpath: /config/devices/entry[@name='localhost.localdomain']/device-group/entry[@name='{{DEVICE_GROUP}}']/profiles/virus - name: panorama_profiles_spyware_10_0 include: panorama_profiles_spyware_10_0 include_variables: all include_snippets: - name: ironskillet_spyware_outbound xpath: /config/devices/entry[@name='localhost.localdomain']/device-group/entry[@name='{{DEVICE_GROUP}}']/profiles/spyware - name: ironskillet_spyware_inbound xpath: /config/devices/entry[@name='localhost.localdomain']/device-group/entry[@name='{{DEVICE_GROUP}}']/profiles/spyware - name: ironskillet_spyware_internal xpath: /config/devices/entry[@name='localhost.localdomain']/device-group/entry[@name='{{DEVICE_GROUP}}']/profiles/spyware - name: ironskillet_spyware_alert_all xpath: /config/devices/entry[@name='localhost.localdomain']/device-group/entry[@name='{{DEVICE_GROUP}}']/profiles/spyware - name: ironskillet_spyware_exception xpath: /config/devices/entry[@name='localhost.localdomain']/device-group/entry[@name='{{DEVICE_GROUP}}']/profiles/spyware - name: panorama_profiles_vulnerability_10_0 include: panorama_profiles_vulnerability_10_0 include_snippets: - name: ironskillet_vulnerability_outbound xpath: /config/devices/entry[@name='localhost.localdomain']/device-group/entry[@name='{{DEVICE_GROUP}}']/profiles/vulnerability - name: ironskillet_vulnerability_inbound xpath: /config/devices/entry[@name='localhost.localdomain']/device-group/entry[@name='{{DEVICE_GROUP}}']/profiles/vulnerability - name: ironskillet_vulnerability_internal xpath: /config/devices/entry[@name='localhost.localdomain']/device-group/entry[@name='{{DEVICE_GROUP}}']/profiles/vulnerability - name: ironskillet_vulnerability_alert_all xpath: /config/devices/entry[@name='localhost.localdomain']/device-group/entry[@name='{{DEVICE_GROUP}}']/profiles/vulnerability - name: panorama_profiles_file_blocking_10_0 include: panorama_profiles_file_blocking_10_0 include_snippets: - name: ironskillet_file_blocking_outbound xpath: /config/devices/entry[@name='localhost.localdomain']/device-group/entry[@name='{{DEVICE_GROUP}}']/profiles/file-blocking - name: ironskillet_file_blocking_inbound xpath: /config/devices/entry[@name='localhost.localdomain']/device-group/entry[@name='{{DEVICE_GROUP}}']/profiles/file-blocking - name: ironskillet_file_blocking_internal xpath: /config/devices/entry[@name='localhost.localdomain']/device-group/entry[@name='{{DEVICE_GROUP}}']/profiles/file-blocking - name: ironskillet_file_blocking_alert_all xpath: /config/devices/entry[@name='localhost.localdomain']/device-group/entry[@name='{{DEVICE_GROUP}}']/profiles/file-blocking - name: panorama_profiles_url_filtering_10_0 include: panorama_profiles_url_filtering_10_0 include_snippets: - name: ironskillet_url_outbound xpath: /config/devices/entry[@name='localhost.localdomain']/device-group/entry[@name='{{DEVICE_GROUP}}']/profiles/url-filtering - name: ironskillet_url_alert_all xpath: /config/devices/entry[@name='localhost.localdomain']/device-group/entry[@name='{{DEVICE_GROUP}}']/profiles/url-filtering - name: ironskillet_url_exception xpath: /config/devices/entry[@name='localhost.localdomain']/device-group/entry[@name='{{DEVICE_GROUP}}']/profiles/url-filtering - name: panorama_profiles_wildfire_analysis_10_0 include: panorama_profiles_wildfire_analysis_10_0 include_snippets: - name: ironskillet_wildfire_outbound xpath: /config/devices/entry[@name='localhost.localdomain']/device-group/entry[@name='{{DEVICE_GROUP}}']/profiles/wildfire-analysis - name: ironskillet_wildfire_inbound xpath: /config/devices/entry[@name='localhost.localdomain']/device-group/entry[@name='{{DEVICE_GROUP}}']/profiles/wildfire-analysis - name: ironskillet_wildfire_internal xpath: /config/devices/entry[@name='localhost.localdomain']/device-group/entry[@name='{{DEVICE_GROUP}}']/profiles/wildfire-analysis - name: ironskillet_wildfire_alert_all xpath: /config/devices/entry[@name='localhost.localdomain']/device-group/entry[@name='{{DEVICE_GROUP}}']/profiles/wildfire-analysis - name: panorama_profile_group_10_0 include: panorama_profile_group_10_0 include_snippets: - name: ironskillet_profile_group_outbound xpath: /config/devices/entry[@name='localhost.localdomain']/device-group/entry[@name='{{DEVICE_GROUP}}']/profile-group - name: ironskillet_profile_group_inbound xpath: /config/devices/entry[@name='localhost.localdomain']/device-group/entry[@name='{{DEVICE_GROUP}}']/profile-group - name: ironskillet_profile_group_internal xpath: /config/devices/entry[@name='localhost.localdomain']/device-group/entry[@name='{{DEVICE_GROUP}}']/profile-group - name: ironskillet_profile_group_alert_all xpath: /config/devices/entry[@name='localhost.localdomain']/device-group/entry[@name='{{DEVICE_GROUP}}']/profile-group - name: ironskillet_profile_group_default xpath: /config/devices/entry[@name='localhost.localdomain']/device-group/entry[@name='{{DEVICE_GROUP}}']/profile-groupNote
It is not currently possible to include another include. This means that a playlist cannot effectively include a snippet from another playlist that already has a
include_snippets:
defined. If this needs to be done, instead try referencing the same sub-skillets directly in both playlists.
Including Variables¶
Generally when including snippets from a sub-skillet, all of the variables from the sub-skillet should be loaded as well, since they are needed to execute the snippets. This is the default action when loading an entire sub-skillet. However, if only certain snippets are loaded, or if changes to the snippet are made in the playlist, it is important to specify how variables are included. Basically, anytime the
include_snippets:
attribute is used,include_variables:
should also be specified, as long as there are variables in the sub-skillet to include.Take the XPath override example from the previous section:
snippets: - name: panorama_tag_10_0 include: panorama_tag_10_0 include_snippets: - name: ironskillet_tag_ironskillet_version xpath: /config/devices/entry[@name='localhost.localdomain']/device-group/entry[@name='{{DEVICE_GROUP}}']/tagThe
panorama_tag_10_0
sub-skillet does not have any variables in it, so in this case, aninclude_variables:
is not necessary. However, if including snippets from a different sub-skillet, andinclude_variables: all
should be added right above theinclude_snippets:
.This also highlights another important factor, which is that any new variables introduced to the playlist in snippet changes must be included in the
variables:
section of the playlist. Here, the DEVICE_GROUP variable should be added to the Panorama Not-Shared Security Profile playlist as follows:variables: - name: DEVICE_GROUP description: Device-group name for Panorama default: sample_devicegroup type_hint: text help_text: creates a sample device-group with IronSkillet configuration elementsThere are a few other use cases that might come up:
Menu options for custom loads (checkboxes in a workflow)
When conditional includes
See the Workflow Tutorial for more examples of variable usage
Test and Troubleshoot¶
Now that the skillet has been pushed to GitHub, the skillet can be imported or loaded into one of the skillet player tools, such as PanHandler or SLI, for testing. This Tutorial will show how to test and debug using PanHandler. Make sure to update PanHandler to the latest release, as playlists are a new feature.
Testing playlists involves three main components:
User-facing variables
Overall sequence of sub-skillets
Overrides of any sub-skillet features
Continue reading to see how to test these components in PanHandler.
Import the Playlists¶
First, import the repository into PanHandler. The Import Playlists option in the PanHandler Menu will take you this page. Then fill out the Repository Name (can be anything you want) and the URL, and hit the Submit button.
If the repository did load correctly, then it should take you to the Repository Detail page. This page has a Details overview section, a preview of the latest commits to the repository, the skillets (and playlists) found and loaded from the repository, and links to the collections found from the skillets. An option to checkout a different branch from the default is all the way at the bottom of the page.
If the playlist did not load correctly into PanHandler, an error message should pop up naming the playlist and snippet where the error occurred. This could be due to an incorrect name reference, missing variables, or general YAML syntax errors. If this happens, fix what was named in the error, commit/push those changes, and then hit the Update To Latest button (green oval) at the top of the repository Details page.
Warning
If there are other repositories (for example PANW IronSkillet) already loaded into PanHandler that have the same skillet names as the playlists, the new playlists will not load. This will not throw an error, so it will appear that the new repository loaded correctly, but it could be missing playlists. To fix this, remove both repositories with duplicate names and try importing the playlist repository again.
Debug and Play the Playlist¶
Once the repository is loaded into PanHandler without any errors, there are a few playlist-specific features that should be double checked. To play a playlist, click on the name of the playlist from the repository Details page, or the Collection page the playlist belongs to (IronSkillet Playlists in this tutorial).
All variables loaded into a skillet will show up in the first menu when evaluating the playlist. The correct variable menus are shown below for each playlist.
PAN-OS Full
PAN-OS Alert Only
Panorama Not-Shared Security Policies
After hitting the Submit button at the bottom right of the variables menu, the Target Information menu will show up. Here, a valid NGFW or Panorama IP, username, and password should be inputted. DO NOT HIT SUBMIT, but instead click the Debug button (pink box). This opens up a super helpful menu that shows how the XML snippets rendered from the playlist and variables specified. This Debug view will be used to double-check three important aspects of playlists below.
The Debug view has a section for each snippet that is included in the playlist. Each of these sections is broken out into the snippet name (blue text), JSON format of what is being loaded, the XPath the configuration will be pushed to (pink text), and then the XML to be pushed (red and black text). An example is shown below for the IronSkillet version tag snippet in the PAN-OS Alert Only playlist.
Note
The blue text is formatted so that is joins the name of the sub-skillet and the name of the snippet with a period. In the example above, the
ironskillet_tag_ironskillet_version
snippet was pulled from thepanos_ngfw_tag_10_0
sub-skillet. This is useful when tracking the location of an error when debugging.Check variables loaded correctly
The first way to check this is through the variable menus shown above. Each menu should list out all variables expected to be included in the playlist, along with any characteristics specified for the variable. If variables are not showing up in the menu as expected, make sure that there is an
include_variables:
specified in the snippet that the variables are pulling from. Alternatively, try adding the variable to thevariables:
section of the playlist.The second way to check the variables is on the Debug page. All XML snippets shown (red and black text) should have the variables populated according to what was specified in the variable menu. No
{{ VARIABLE_NAME }}
text should be left. For example, in the PAN-OS Full playlist, The primary and secondary DNS servers have been specified.Check XML snippets and XPaths loaded correctly
When only including certain snippets from a sub-skillet, it is good practice to confirm that only those snippets were loaded from the playlist. Using the PAN-OS Alert-Only playlist, it is easy to confirm that only the Alert profiles were loaded for each of the Security Policy sub-skillets. There should be 10 snippets total (8 alert policies, alert profile group, and the IronSkillet tag). When scrolling down the Debug page, there should only be 10 sections that start with the blue text header (which indicates 10 snippets loaded).
Confirm XPath and XML overrides
If specifying a different XPath or XML for a snippet than is pre-defined in the sub-skillet, it is a good idea to confirm that those changes went through. For XPaths, this is simple to view on the Debug page, as each snippet loaded has the XPath tied to it in pink text. As can be seen below, the Not-Shared Panorama XPaths for device group went through, and the variable loaded in correctly.
Any XML overrides specified can also be confirmed in the same manner as the XPath. Double check that the XML loaded matches what is explicitly written in the playlist versus what would normally be included from the sub-skillet.
Some common errors are:
Using the incorrect sub-skillet or snippet name in an
include_snippets:
attributeNot including all variables needed
Using the same name between sub-skillets and playlists, or between separate repositories loaded in PanHandler
Edit, Push, Test¶
As changes are made to the skillets while debugging, the following steps should be taken to see the changes reflected in PanHandler:
Commit and Push changes from the IDE/code editor to GitHub
In PanHandler open up the Imported Repositories page using the Skillet Repositories menu option (blue box)
Once on the Imported Repositories page, there are two options to update:
Open up the playlist repository using the Details button (green box below) and click the Update to Latest button (green oval below) OR
Click the Update All Repositories button (purple box below)
Imported Repositories Page
Details Page
Document¶
The final stage is to document key details about the skillet to provide contextual information to the user community. Documentation is especially important when using the Playlist Framework, as there is additional content being included and referenced through the submodule and sub-skillets.
README.md¶
The playlist repository has an empty placeholder
README.md
that should give an overview of the solution. TheREADME.md
should provide skillet-specific details such as what the playlist does, variable input descriptions, and caveats and requirements. Some playlist-specific information to include:
Information about the submodules and the content they contain
A reminder that when cloning a repository with a submodule, existing submodules will need to be initiated and updated before use. To do this, run the following commands:
Clone the repository
git clone <clone_link>
git submodule init
git submodule update
Remind users to update the submodule as needed, since that is not done automatically as new commits are released. To do this, run the following commands:
Open the playlist repository
Run
git submodule update --remote --merge
Commit and Push any changes
README.md
uses the markdown formatting language. Numerous examples can be found in the skillet files. There is also a wide array of markdown cheat sheets you can find using Google searches. Below are a few common markdown elements you can use in your documentation. Most IDEs can display the user view as you edit the markdown file.
Markdown syntax options
#, ##, ### for header text levels (H1, H2, H3, etc.)
**text** for bold text
*text* or _text_ to underline
1. text to create numbered lists
* text, + text, - text for bullet style lists
[text](url) for inline web links
`test` to highlight a text string
```text block - one or more lines``` to create a highlighted text block
Tip
To view markdown edits for existing GitHub repos, click on the README.md file, then use the Raw option to display the output as raw markdown text. From here, you can copy and paste or review formatting.
Paste this sample
README.md
file into your repository and push to GitHub.# Sample Playlist Includes Skillet This is used in the training material as part of the Playlist Includes tutorial. The solution utilizes three playlists: 1. A full IronSkillet PAN-OS 10.0 configuration 2. An Alert-Only Security Profiles IronSkillet PAN-OS 10.0 configuration * only includes Alert-Only Security Profiles * the IronSkillet version tag is included for documentation purposes 3. A IronSkillet Not-Shared Panorama 10.0 Security Profiles configuration * only includes Security Profiles for a Not-Shared Panorama configuration * the IronSkillet version tag is included for documentation purposes These playlists were based off of some of the playlists in the [IronSkillet 10.1 branch](https://github.com/PaloAltoNetworks/iron-skillet/tree/panos_v10.1/playlists). Check out the [README](https://github.com/PaloAltoNetworks/iron-skillet/blob/panos_v10.1/playlists/README.md) for more information on the playlists and content they contain. Configuration elements in the playlists pull from the [ironskillet-components](https://github.com/PaloAltoNetworks/ironskillet-components) submodule, which has several sub-skillets. All skillet-player tools (PanHandler, SLI, etc.) will be able to read in the snippets from the sub-skillets in the submodule using the `include_snippets` attribute in the playlists. However, the submodule has a few steps for upkeep when using content locally. When cloning this repository, the submodule will need to be initiated and updated before being able to use it. To do this, run the following commands: * Clone the repository: `git clone <clone_link>` * Initiate the submodule: `git submodule init` * Update the submodule to the latest commit: `git submodule update` It is also recommended to update the submodule as needed (not done automatically as new commits are released). It is necessary to commit and push changes in order to see the latest commit pulled into a skillet player. This can be done using the following steps: * Open the repository * Update the submodule: `git submodule update --remote --merge` * Commit and Push any changesSupport Policy Text
Skillets are not part of Palo Alto Networks supported product so the policy text is appended to the README file to specify skillets are not supported. Sample text to copy/paste is found in the SkilletBuilder repo README
LIVEcommunity¶
Playlists can be shared in the Live community as Community or Personal skillets. Community Skillets are expected to have a higher quality of testing, documentation, and ongoing support. Personal skillets can be shared as-is to create awareness and eventually become upgraded as Community Skillets.
Other Applications¶
With the Playlist Framework, there are many new options for how skillets can be built. A few further ideas to spark inspiration are listed below.
Any repo with developed skillets can be added as a submodule
Existing skillets can be broken into smaller sub-skillets and included in a playlist
If submodules are too complex, the sub-skillets can be added directly to the host repository
Playlist Includes can see any sub-skillets within the playlist repository directory or submodule
More than one submodule can be added to a repository
Feel free to reach out with any questions! See the Feedback Section for more information on how to do so.
Example Skillet¶
In this example, we will create a skillet that allows the user to customize a single variable.
XML Fragment¶
First, we’ll extract the parts of the configuration that comprise this ‘unit’ of configuration changes (a skillet). For example, this portion of the configuration describes the log-settings we would like to modify:
<system>
<match-list>
<entry name="dhcp-log-match">
<send-syslog>
<member>mgmt-interface</member>
</send-syslog>
<filter>(eventid eq lease-start)</filter>
</entry>
</match-list>
</system>
<syslog>
<entry name="mgmt-interface">
<server>
<entry name="mgmt-intf">
<transport>UDP</transport>
<port>514</port>
<format>BSD</format>
<server>{{ MGMT_IP }}</server>
<facility>LOG_USER</facility>
</entry>
</server>
</entry>
</syslog>
Notice here we have defined one variable: MGMT_IP. This will allow the user to insert their own management ip when deploying.
.meta-cnc file¶
name: example_log_setting
label: Log Setting Example
description: Example log setting to configure syslog
type: panos
extends:
labels:
service_type: userid
variables:
- name: MGMT_IP
description: NGFW management IP address
default: 192.168.0.1
type_hint: ip_address
snippets:
- name: log_settings
cmd: set
xpath: /config/shared/log-settings
file: log_settings.xml
In this file, we give some basic information about what this skillet will do, what configuration bits will be applied, and what variables the user can customize. Notice in the ‘variables’ section, we specify a variable entry with a ‘name’ that matches the variable defined in the XML fragment. The ‘snippets’ section will inform Panhandler where in the configuration this fragment should be inserted (xpath) and where to find the fragment (file).
Example Validation Skillet¶
This is a very basic example showing validate a portion of a PAN-OS configuration. Often times, you need to check for specific values or apply some simple logic to a portion of the config to determine if it is considered compliant or not. Skillets of type pan_validation allow you to do just that.
By default, Panhandler will always supply a variable called ‘config’ that contains the NGFW running config. The parse cmd can be used to pull out and capture specific parts of that config. In this example, we use an advanced xpath query to return a variable containing a list of all zone names configured in the running config. Another advanced xpath is also used to find an ethernet interface with a specific IP Address. That interface is converted to an object using capture_object.
The snippets with a cmd type of validate is where the actual compliance checks are performed. The test attribute will be evaluated as a jinja boolean expression. True values are considered to have ‘passed’ this test.
.meta-cnc.yaml¶
#
# Example Validation Skillet
#
name: example-validate-with-xpath-capture
label: Example of how to use xpath queries to capture specific items of interest.
description: |
This example Skillet shows how to parse and validate a config using xpath syntax. This example checks the
configured zones to ensure we do not have one with the attribute name equal to 'does-not-exist'
type: pan_validation
labels:
collection:
- Example Skillets
- Validation
variables:
# this will allow the user to input a zone name to test
- name: zone_to_test
description: Name of the Zone to test for absence
default: does-not-exist
type_hint: text
# as well as an IP address to search for as well
- name: ip_to_find
description: IP Address to locate
default: 10.10.10.10/24
type_hint: ip_address
snippets:
- name: parse config variable and capture outputs
cmd: parse
variable: config
outputs:
# create a variable named 'zone_names' which will be a list of the attribute 'names' from each zone
# note the use of '//' to select all zones
# the '@name' will return only the value of the attribute 'name' from each 'entry'
- name: zone_names
capture_pattern: /config/devices/entry/vsys/entry/zone//entry/@name
# note here we can combine an advanced xpath query with 'capture_object'. This will capture
# the full interface definition from the interface that contains the 'ip_to_find' value
- name: interface_with_ip
capture_object: /config/devices/entry/network/interface/ethernet//entry/layer3/ip/entry[@name="{{ ip_to_find }}"]/../..
# simple test using a jinja expression to verify the 'zone_to_test' variable is not in the 'zone_names' test
- name: ensure_desired_zone_absent_from_list
# pan_validation skillet have a default cmd of 'validate'
cmd: validate
# note here that you can use jinja variable interpolation just about anywhere
label: Ensures the {{ zone_to_test }} zone is not configured
test: zone_test_test not in zone_names
fail_message: |
This fail message contains a variable, which is useful for debugging and testing.
captured values were: {{ zone_names | tojson() }} and {{ interface_with_ip | default('none')| tojson() }}
# documentation link helps give the user some context about why this test failed or how to manually remediate
documentation_link: https://github.com/PaloAltoNetworks/skilletlib/blob/develop/docs/source/examples.rst
More Example Skillets¶
Example Skillets by Type¶
Example Skillets by Feature¶
Example Complex Validation Skillet
External Skillet Repositories¶
Here is a couple of Git repositories that contain numerous example Skillets.
SkilletLib is a library for parsing and executing Skillets in third party applications and tooling. The SkilletLib repository also has many useful examples.
Palo Alto Networks World Wide CE team has a great collection of Skillets on Github.
Many other Skillets may be found on Github as well using the Skillets topic.