Examples

Add security policy to Firewall or Panorama

Security policies allow you to enforce rules and take action, and can be as general or specific as needed. The policy rules are compared against the incoming traffic in sequence, and because the first rule that matches the traffic is applied, the more specific rules must precede the more general ones.

Firewall

- name: Add test rule 1 to the firewall
  paloaltonetworks.panos.panos_security_rule:
    provider: '{{ provider }}'
    rule_name: 'Ansible test 1'
    description: 'An Ansible test rule'
    source_zone: ['internal']
    destination_zone: ['external']
    source_ip: ['1.2.3.4']
    source_user: ['any']
    destination_ip: ['any']
    category: ['any']
    application: ['any']
    service: ['service-http']
    hip_profiles: ['any']
    action: 'allow'
    commit: 'False'

Panorama

- name: Add test pre-rule to Panorama
  paloaltonetworks.panos.panos_security_rule:
    provider: '{{ provider }}'
    rule_name: 'Ansible test 1'
    description: 'An Ansible test pre-rule'
    source_zone: ['internal']
    destination_zone: ['external']
    source_ip: ['1.2.3.4']
    source_user: ['any']
    destination_ip: ['any']
    category: ['any']
    application: ['any']
    service: ['service-http']
    hip_profiles: ['any']
    action: 'allow'
    device_group: 'DeviceGroupA'
    commit: False

Add NAT policy to Firewall or Panorama

If you define Layer 3 interfaces on the firewall, you can configure a Network Address Translation (NAT) policy to specify whether source or destination IP addresses and ports are converted between public and private addresses and ports. For example, private source addresses can be translated to public addresses on traffic sent from an internal (trusted) zone to a public (untrusted) zone. NAT is also supported on virtual wire interfaces.

Firewall

- name: Add the service object to the firewall first
  paloaltonetworks.panos.panos_service_object:
    provider: '{{ provider }}'
    name: 'service-tcp-221'
    protocol: 'tcp'
    destination_port: '221'
    description: 'SSH on port 221'
    commit: false

- name: Create dynamic NAT rule on the firewall
  paloaltonetworks.panos.panos_nat_rule:
    provider: '{{ provider }}'
    rule_name: 'Web SSH inbound'
    source_zone: ['external']
    destination_zone: 'external'
    source_ip: ['any']
    destination_ip: ['10.0.0.100']
    service: 'service-tcp-221'
    snat_type: 'dynamic-ip-and-port'
    snat_interface: ['ethernet1/2']
    dnat_address: '10.0.1.101'
    dnat_port: '22'

Panorama

- name: Add the necessary service object to Panorama first
  paloaltonetworks.panos.panos_object:
    provider: '{{ provider }}'
    name: 'service-tcp-221'
    protocol: 'tcp'
    destination_port: '221'
    description: 'SSH on port 221'
    commit: false
    device_group: 'shared_services_11022'

- name: Create dynamic NAT rule on Panorama
  paloaltonetworks.panos.panos_nat_rule:
    provider: '{{ provider }}'
    rule_name: 'Web SSH inbound'
    source_zone: ['external']
    destination_zone: 'external'
    source_ip: ['any']
    destination_ip: ['10.0.0.100']
    service: 'service-tcp-221'
    snat_type: 'dynamic-ip-and-port'
    snat_interface: ['ethernet1/2']
    dnat_address: '10.0.1.101'
    dnat_port: '22'
    device_group: 'shared_services_11022'

Change firewall admin password using SSH

Change admin password of PAN-OS device using SSH with SSH key. This is used in particular when NGFW is deployed in the cloud (such as AWS).

- name: Change user password using ssh protocol
  paloaltonetworks.panos.panos_admpwd:
    ip_address: '{{ ip_address }}'
    username: '{{ username }}'
    newpassword: '{{ new_password }}'
    key_filename: '{{ key_filename }}'

Generates self-signed certificate

This module generates a self-signed certificate that can be used by GlobalProtect client, SSL connector, or otherwise. Root certificate must be preset on the system first. This module depends on paramiko for ssh.

- name: generate self signed certificate
  paloaltonetworks.panos.panos_cert_gen_ssh:
    ip_address: "{{ ip_address }}"
    username: "{{ username }}"
    password: "{{ password }}"
    cert_cn: "{{ cn }}"
    cert_friendly_name: "{{ friendly_name }}"
    signed_by: "{{ signed_by }}"

Check if FW is ready

Check if PAN-OS device is ready for being configured (no pending jobs). The check could be done once or multiple times until the device is ready.

- name: Wait for FW reboot
  paloaltonetworks.panos.panos_check:
    provider: '{{ provider }}'
  register: result
  until: not result|failed
  retries: 50
  delay: 5

Import configuration

Import file into PAN-OS device.

- name: import configuration file into PAN-OS
  paloaltonetworks.panos.panos_import:
    ip_address: "{{ ip_address }}"
    username: "{{ username }}"
    password: "{{ password }}"
    file: "{{ config_file }}"
    category: "configuration"

DHCP on data port

Configure data-port (DP) network interface for DHCP. By default DP interfaces are static.

- name: enable DHCP client on ethernet1/1 in zone external
  paloaltonetworks.panos.panos_interface:
    provider: '{{ provider }}'
    if_name: "ethernet1/1"
    zone_name: "external"
    create_default_route: "yes"
    commit: False

Load configuration

This is example playbook that imports and loads firewall configuration from a configuration file

- name: import config
  hosts: my-firewall
  connection: local
  gather_facts: False

  vars:
    cfg_file: candidate-template-empty.xml

  roles:
    - role: PaloAltoNetworks.paloaltonetworks

  tasks:
  - name: Grab the credentials from ansible-vault
    include_vars: 'firewall-secrets.yml'
    no_log: 'yes'

  - name: wait for SSH (timeout 10min)
    wait_for: port=22 host='{{ provider.ip_address }}' search_regex=SSH timeout=600

  - name: checking if device ready
    paloaltonetworks.panos.panos_check:
      provider: '{{ provider }}'
    register: result
    until: not result|failed
    retries: 10
    delay: 10

  - name: import configuration
    paloaltonetworks.panos.panos_import:
      ip_address: '{{ provider.ip_address }}'
      username: '{{ provider.username }}'
      password: '{{ provider.password }}'
      file: '{{cfg_file}}'
      category: 'configuration'
    register: result

  - name: load configuration
    paloaltonetworks.panos.panos_loadcfg:
      ip_address: '{{ provider.ip_address }}'
      username: '{{ provider.username }}'
      password: '{{ provider.password }}'
      file: '{{result.filename}}'
      commit: False

  - name: set admin password
    paloaltonetworks.panos.panos_administrator:
      provider: '{{ provider }}'
      admin_username: 'admin'
      admin_password: '{{ provider.password }}'
      superuser: True
      commit: False

  - name: commit (blocks until finished)
    paloaltonetworks.panos.panos_commit:
      provider: '{{ provider }}'

Event-Driven Ansible (EDA)

Event-Driven Ansible is a responsive automation solution that can process events containing discrete, actionable intelligence. The extensions/plugins/event_source/logs.py plugin is capable of receiving JSON structured messages from a PAN-OS firewall, restructures the payload as a Python dictionary, determines the appropriate response to the event, and then executes automated actions to address or remediate based on the situation.

There are four components needed to implement this example EDA use case with PAN-OS:

  • HTTP server profile: A PAN-OS firewall configuration that defines how the PAN-OS firewall(s) should send events to the EDA server.

  • EDA rulebook: A YAML file which describes events of interest, and how to EDA respond to them based on conditions.

  • Inventory: A YAML file that defines the PAN-OS firewall(s) to be executed against when a condition is met.

  • Ansible playbook: A YAML file that defines the Ansible tasks to be executed when a condition is met.

The four components are described here in the context of a use case of detecting decryption issues based on the Decryption Logs, and responding by placing the relevant URLs into a category used for decryption bypass.

HTTP Server Profile

The following example shows what a Decryption HTTP server profile would look like in PAN-OS. The HTTP server profile is configured to send logs to the EDA server.

{
    "category": "network",
    "details": {
        "action": "$action",
        "app": "$app",
        "cn": "$cn",
        "dst": "$dst",
        "device_name": "$device_name",
        "error": "$error",
        "issuer_cn": "$issuer_cn",
        "root_cn": "$root_cn",
        "root_status": "$root_status",
        "sni": "$sni",
        "src": "$src",
        "srcuser": "$srcuser"
    },
    "receive_time": "$receive_time",
    "rule": "$rule",
    "rule_uuid": "$rule_uuid",
    "serial": "$serial",
    "sessionid": "$sessionid",
    "severity": "informational",
    "type": "decryption"
}

This HTTP Server Profile could be configured in its entirety using the following tasks in an Ansible playbook:

- name: Create a HTTP Server Profile for Decryption Logs
  paloaltonetworks.panos.panos_http_profile:
    provider: '{{ device }}'
    name: '{{ server_profile_name_decrypt }}'
    decryption_name: 'decryption-logs-to-eda'
    decryption_uri_format: 'https://test'
    decryption_payload: >
      {
          "category": "network",
          "details": {
              "action": "$action",
              "app": "$app",
              "cn": "$cn",
              "dst": "$dst",
              "device_name": "$device_name",
              "error": "$error",
              "issuer_cn": "$issuer_cn",
              "root_cn": "$root_cn",
              "root_status": "$root_status",
              "sni": "$sni",
              "src": "$src",
              "srcuser": "$srcuser"
          },
          "receive_time": "$receive_time",
          "rule": "$rule",
          "rule_uuid": "$rule_uuid",
          "serial": "$serial",
          "sessionid": "$sessionid",
          "severity": "informational",
          "type": "decryption"
      }

- name: Create HTTP server
  paloaltonetworks.panos.panos_http_server:
    provider: '{{ device }}'
    http_profile: '{{ server_profile_name_decrypt }}'
    name: 'my-EDA-server'
    address: '192.168.1.5'
    http_method: 'GET'
    http_port: 5000

- name: Add a HTTP header to HTTP Server Profile
  paloaltonetworks.panos.panos_http_profile_header:
    provider: '{{ device }}'
    http_profile: '{{ server_profile_name_decrypt }}'
    log_type: 'decryption'
    header: 'Content-Type'
    value: 'application/json'

- name: Add a param to the config log type
  paloaltonetworks.panos.panos_http_profile_param:
    provider: '{{ device }}'
    http_profile: '{{ server_profile_name_decrypt }}'
    log_type: 'decryption'
    param: 'serial'
    value: '$serial'

The HTTP Server Profile would be used in a Log Forwarding Profile with a filter for only forwarding Decryption Logs when there has been an issue with decryption. Here are example Ansible tasks to create a Log Forwarding Profile:

- name: Create log forwarding profile
  paloaltonetworks.panos.panos_log_forwarding_profile:
    provider: '{{ provider }}'
    name: 'EDA_LFP'
    enhanced_logging: true

- name: Create log forwarding profile match list
  paloaltonetworks.panos.panos_log_forwarding_profile_match_list:
    provider: '{{ provider }}'
    log_forwarding_profile: 'EDA_LFP'
    name: 'eda-decryption-forwarding'
    log_type: 'decryption'
    filter: '( err_index neq None ) and ( proxy_type eq Forward )'
    http_profiles: ['{{ server_profile_name_decrypt }}']

Rulebook - rulebook.yml

This rulebook shows an example of how to configure EDA to receive the decryption logs from PAN-OS, and execute a remediation playbook:

---
- name: "Receive logs sourced from HTTP Server Profile in PAN-OS"
  hosts: "localhost"

  ## Define how our plugin should listen for logs from PAN-OS
  sources:
    - paloaltonetworks.panos.logs:
        host: 0.0.0.0
        port: 5000
        type: decryption

  ## Define the conditions we are looking for. There are many types of logs
  ## in PAN-OS; we are looking just for decryption logs
  rules:
    - name: "Troubleshoot Decryption Failure"
      condition: event.meta.log_type == "decryption"

      ## Define the action we should take should the condition be met,
      ## when we find a decryption log, which is to execute the
      ## remediation playbook
      action:
        run_playbook:
          name: "playbooks/decryption_remediation.yml"

Inventory

The inventory for this example use case is one that defines all hosts (firewalls) to be local connectivity, as this is how Ansible communicates with PAN-OS:

all:
  hosts:
    localhost:
      ansible_connection: local

Playbook - decryption_remediation.yml

The playbook executed when the conditions in the rulebook are met, in this example use case, performs tasks to add the relevant URL into a category used to bypass decryption, thus remediating the problem:

---
- name: Decryption Remediation Playbook
  hosts: 'all'
  gather_facts: false
  connection: local

  vars:
    device:
      ip_address: "192.168.1.10"
      username: "admin"
      password: "redacted"

    bypass_category_name: 'decryption-bypass'


  ## When EDA calls this playbook for execution, it takes the SNI (Server Name Indication)
  ## from the decryption logs where a site failed to be decrypted properly, and adds the
  ## SNI to the list of domains in a URL category. This URL category is used as match
  ## criteria, therefore domains in this URL category will no longer be decrypted by the
  ## decryption policy rule.

  tasks:
    ## Gather up the list of domains currently in the URL category
    - name: Get current decryption bypass domains
      paloaltonetworks.panos.panos_custom_url_category:
        provider: "{{ device }}"
        state: "gathered"
        gathered_filter: "name == '{{ bypass_category_name }}'"
      register: bypass_category

    ## If the URL category already has some domains, add this SNI to the list ('url_value')
    - name: Update decryption bypass category with new domain, if category is currently not empty
      paloaltonetworks.panos.panos_custom_url_category:
        provider: '{{ device }}'
        name: '{{ bypass_category_name }}'
        url_value: '{{ bypass_category.gathered[0].url_value + [ansible_eda.event.payload.details.sni] }}'
      when:
        - bypass_category.gathered[0].url_value != None
        - ansible_eda.event.payload.details.sni not in bypass_category.gathered[0].url_value

    ## If the URL category is empty, create the list ('url_value') with this SNI
    - name: Create decryption bypass category with new domain, if category is currently empty
      paloaltonetworks.panos.panos_custom_url_category:
        provider: '{{ device }}'
        name: '{{ bypass_category_name }}'
        url_value: '{{ [ansible_eda.event.payload.details.sni] }}'
      when:
        - bypass_category.gathered[0].url_value == None

    ## Having added the site's SNI to the URL category, make this change live by performing a 'commit'
    - name: Commit configuration
      paloaltonetworks.panos.panos_commit_firewall:
        provider: "{{ device }}"
      register: results

    ## Output results of the commit
    - name: Output commit results
      ansible.builtin.debug:
        msg: "Commit with Job ID: {{ results.jobid }} had output: {{ results }}"

An alternative remediation if the web server hosting the URL is not presenting the relevant intermediate certificate, would be to add the intermediate certificate into the PAN-OS certificate store, and not use a bypass (which weakens visibility by leaving more traffic encrypted) like the previous example:

tasks:
  - name: Get intermediate certificate URL
    ansible.builtin.set_fact:
      intermediate_cert_url: "{{ ansible_eda.event.payload.details.error | regex_search(regex_query, ignorecase=True) }}"
    vars:
      regex_query: '(http(s)?:\/\/.)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)'

  - name: Get intermediate certificate filename
    ansible.builtin.set_fact:
      intermediate_cert_name: "{{ intermediate_cert_url | regex_search(regex_query, ignorecase=True) }}"
    vars:
      regex_query: '[^\/\\&\?]+\.\w{3,4}(?=([\?&].*$|$))'

  - name: Download intermediate certificate
    ansible.builtin.get_url:
      url: '{{ intermediate_cert_url }}'
      dest: '{{ intermediate_cert_name }}'

  - name: Convert intermediate certificate from DER format to PEM format
    ansible.builtin.command: openssl x509 -inform DER -outform PEM -in {{ intermediate_cert_name }} -out {{ intermediate_cert_name }}.pem
    register: output
    changed_when: output.rc != 0

  - name: Import intermediate certificate to NGFW
    paloaltonetworks.panos.panos_import:
      provider: '{{ device }}'
      category: 'certificate'
      certificate_name: '{{ intermediate_cert_name }}'
      format: 'pem'
      filename: '{{ intermediate_cert_name }}.pem'