Ansible playbook timeout - panos_import - xapi.py in keygen

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 
Announcements
Please sign in to see details of an important advisory in our Customer Advisories area.

Ansible playbook timeout - panos_import - xapi.py in keygen

L0 Member

I'm getting a timeout on the panos_import command.

 

I see a reference to xapi.keygen() in the traceback... Showing my playbook and the traceback below.

 

Thanks for any tips,
Chris.

(spoiler tags? code blocks?)

 

The Playbook:

Spoiler

 

---

- name: Initialize the Palo Alto Networks firewall
  hosts: localhost
  connection: local
  gather_facts: False

  roles:
    - role: PaloAltoNetworks.paloaltonetworks

  vars:
    config_file: "/opt/ansible/files/pan/PA-test-Preliminary.xml"
    ip_address: 10.255.5.45
    username: "admin"
    password: "wackydoo"

  tasks:

  - name: set admin password
    panos_admpwd:
      ip_address: "{{ ip_address }}"
      key_filename: "/home/me/.ssh/my.pem"
      username: "{{ username }}"
      newpassword: "{{ password }}"

  - name: import configuration xml file into PAN-OS
    panos_import:
      ip_address: "{{ ip_address }}"
      username: "{{ username }}"
      password: "{{ password }}"
      file: "{{ config_file }}"
      category: "configuration"
    register: result

  - name: load configuration
    panos_loadcfg:
      ip_address: "{{ ip_address }}"
      password: "{{ password }}"
      file: "{{result.filename}}"

 

 

 

The error with -vvv on the command line:

 

Spoiler

 

The full traceback is:
  File "/tmp/ansible_aAF16c/ansible_module_panos_import.py", line 179, in main
    changed, filename = import_file(xapi, module, ip_address, file_, category)
  File "/tmp/ansible_aAF16c/ansible_module_panos_import.py", line 98, in import_file
    xapi.keygen()
  File "/usr/lib/python2.7/site-packages/pan/xapi.py", line 637, in keygen
    raise PanXapiError(self.status_detail)

fatal: [localhost]: FAILED! => {
    "changed": false,
    "invocation": {
        "module_args": {
            "category": "configuration",
            "file": "/opt/ansible/files/pan/PA-test-Preliminary.xml",
            "ip_address": "10.255.5.45",
            "password": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER",
            "url": null,
            "username": "admin"
        }
    },
    "msg": "URLError: reason: [Errno 110] Connection timed out"
}

 

1 accepted solution

Accepted Solutions

L1 Bithead

Hi there!
I'm not sure about the error you are getting.

 

If it was my case, I would start by testing the import with something that works.

I downloaded the cfg of the firewall, made some changes, imported the cfg and it worked like a charm.
I did the same with the OS file too. (600MB)

Go to Device -> Setup -> Operations -> Save named config.. -> use a short name with no spaces (firewall_cfg)

Then Device -> Setup -> Operations -> Export named config.. -> use the same name

 

This is the playbook I'm using:

Spoiler
---
# how to run this
# ansible-playbook panos_pbk_11_load_cfg.yml --extra-vars "hosts_to_use='fwpa01' cfg_location_to_use='../test_files/dxcfwpasbx01_fresh_config'"  -vvvv

# to solve ssl issue see file ../how_to_fix_ssl_certificate_issue.txt

# to delete a saved configuration file
# delete config saved <filename>

- name: Load configuration on PAN-OS device
  hosts: "{{ hosts_to_use }}"
  gather_facts: false
  connection: local

  vars_files:
  - /home/MYUSERNAME/ansible/vault.yml
#  - ../vault.yml

  tasks:

# Import and load config file from URL. The import is a separated module
  - name: import configuration.
    panos_import:
      ip_address: "{{ansible_host}}"
      username: "{{vault_fwpa01_username}}"
      password: "{{vault_fwpa01_password}}"
#      url: "{{cfg_location_to_use}}"  #  url must be like http:///home/MYUSERNAME/ansible/panos_dependencies/testcfg_dxcfwpasbx01
      file: "{{cfg_location_to_use}}"
      category: "configuration"
    register: result

  - name: load configuration
    panos_loadcfg:
      ip_address: "{{ansible_host}}"
      username: "{{vault_fwpa01_username}}"
      password: "{{vault_fwpa01_password}}"
      file: "{{result.filename}}"
      commit: "False"

This is the module I'm using: (Just in case it is different for some reason)

cat /usr/lib/python2.7/site-packages/ansible/modules/network/panos/panos_import.py

Spoiler
#!/usr/bin/env python

#  Copyright 2016 Palo Alto Networks, Inc
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

DOCUMENTATION = '''
---
module: panos_import
short_description: import file on PAN-OS devices
description:
    - Import file on PAN-OS device
author: "Luigi Mori (@jtschichold), Ivan Bojer (@ivanbojer)"
version_added: "2.3"
requirements:
    - pan-python
    - requests
    - requests_toolbelt
options:
    ip_address:
        description:
            - IP address (or hostname) of PAN-OS device.
        required: true
    password:
        description:
            - Password for device authentication.
        required: true
    username:
        description:
            - Username for device authentication.
        required: false
        default: "admin"
    category:
        description:
            - Category of file uploaded. The default is software.
        required: false
        default: software
    file:
        description:
            - Location of the file to import into device.
        required: false
        default: None
    url:
        description:
            - URL of the file that will be imported to device.
        required: false
        default: None
'''

EXAMPLES = '''
# import software image PanOS_vm-6.1.1 on 192.168.1.1
- name: import software image into PAN-OS
  panos_import:
    ip_address: 192.168.1.1
    username: admin
    password: admin
    file: /tmp/PanOS_vm-6.1.1
    category: software
'''

RETURN='''
# Default return values
'''

ANSIBLE_METADATA = {'metadata_version': '1.1',
                    'status': ['preview'],
                    'supported_by': 'community'}

from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import get_exception

import os.path
import xml.etree
import tempfile
import shutil
import os

try:
    import pan.xapi
    import requests
    import requests_toolbelt
    HAS_LIB = True
except ImportError:
    HAS_LIB = False


def import_file(xapi, module, ip_address, file_, category):
    xapi.keygen()

    params = {
        'type': 'import',
        'category': category,
        'key': xapi.api_key
    }

    filename = os.path.basename(file_)

    mef = requests_toolbelt.MultipartEncoder(
        fields={
            'file': (filename, open(file_, 'rb'), 'application/octet-stream')
        }
    )

    r = requests.post(
        'https://'+ip_address+'/api/',
        verify=False,
        params=params,
        headers={'Content-Type': mef.content_type},
        data=mef
    )

    # if something goes wrong just raise an exception
    r.raise_for_status()

    resp = xml.etree.ElementTree.fromstring(r.content)

    if resp.attrib['status'] == 'error':
        module.fail_json(msg=r.content)

    return True, filename


def download_file(url):
    r = requests.get(url, stream=True)
    fo = tempfile.NamedTemporaryFile(prefix='ai', delete=False)
    shutil.copyfileobj(r.raw, fo)
    fo.close()

    return fo.name


def delete_file(path):
    os.remove(path)


def main():
    argument_spec = dict(
        ip_address=dict(required=True),
        password=dict(required=True, no_log=True),
        username=dict(default='admin'),
        category=dict(default='software'),
        file=dict(),
        url=dict()
    )
    module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, required_one_of=[['file', 'url']])
    if not HAS_LIB:
        module.fail_json(msg='pan-python, requests, and requests_toolbelt are required for this module')

    ip_address = module.params["ip_address"]
    password = module.params["password"]
    username = module.params['username']

    xapi = pan.xapi.PanXapi(
        hostname=ip_address,
        api_username=username,
        api_password=password
    )

    file_ = module.params['file']
    url = module.params['url']

    category = module.params['category']

    # we can get file from URL or local storage
    if url is not None:
        file_ = download_file(url)

    try:
        changed, filename = import_file(xapi, module, ip_address, file_, category)
    except Exception:
        exc = get_exception()
        module.fail_json(msg=exc.message)

    # cleanup and delete file if local
    if url is not None:
        delete_file(file_)

    module.exit_json(changed=changed, filename=filename, msg="okey dokey")

if __name__ == '__main__':
    main()

On my inventory file (called hosts) I'm using:

Spoiler
#Palo Alto Firewall VM
[firewalls]
fwpa01 ansible_host=XXX.XXX.XXX.XXX ansible_user=testadmin

And on my vault:

Spoiler
---
vault_fwpa01_username: 'adminaccount'
vault_fwpa01_password: 'accountpassword'

You could give it a try with that and see if it works.

Also, I remember I used a dsa key on my firewall for authentication, but I think it is not used on this scenario.

 

 

 

View solution in original post

1 REPLY 1

L1 Bithead

Hi there!
I'm not sure about the error you are getting.

 

If it was my case, I would start by testing the import with something that works.

I downloaded the cfg of the firewall, made some changes, imported the cfg and it worked like a charm.
I did the same with the OS file too. (600MB)

Go to Device -> Setup -> Operations -> Save named config.. -> use a short name with no spaces (firewall_cfg)

Then Device -> Setup -> Operations -> Export named config.. -> use the same name

 

This is the playbook I'm using:

Spoiler
---
# how to run this
# ansible-playbook panos_pbk_11_load_cfg.yml --extra-vars "hosts_to_use='fwpa01' cfg_location_to_use='../test_files/dxcfwpasbx01_fresh_config'"  -vvvv

# to solve ssl issue see file ../how_to_fix_ssl_certificate_issue.txt

# to delete a saved configuration file
# delete config saved <filename>

- name: Load configuration on PAN-OS device
  hosts: "{{ hosts_to_use }}"
  gather_facts: false
  connection: local

  vars_files:
  - /home/MYUSERNAME/ansible/vault.yml
#  - ../vault.yml

  tasks:

# Import and load config file from URL. The import is a separated module
  - name: import configuration.
    panos_import:
      ip_address: "{{ansible_host}}"
      username: "{{vault_fwpa01_username}}"
      password: "{{vault_fwpa01_password}}"
#      url: "{{cfg_location_to_use}}"  #  url must be like http:///home/MYUSERNAME/ansible/panos_dependencies/testcfg_dxcfwpasbx01
      file: "{{cfg_location_to_use}}"
      category: "configuration"
    register: result

  - name: load configuration
    panos_loadcfg:
      ip_address: "{{ansible_host}}"
      username: "{{vault_fwpa01_username}}"
      password: "{{vault_fwpa01_password}}"
      file: "{{result.filename}}"
      commit: "False"

This is the module I'm using: (Just in case it is different for some reason)

cat /usr/lib/python2.7/site-packages/ansible/modules/network/panos/panos_import.py

Spoiler
#!/usr/bin/env python

#  Copyright 2016 Palo Alto Networks, Inc
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

DOCUMENTATION = '''
---
module: panos_import
short_description: import file on PAN-OS devices
description:
    - Import file on PAN-OS device
author: "Luigi Mori (@jtschichold), Ivan Bojer (@ivanbojer)"
version_added: "2.3"
requirements:
    - pan-python
    - requests
    - requests_toolbelt
options:
    ip_address:
        description:
            - IP address (or hostname) of PAN-OS device.
        required: true
    password:
        description:
            - Password for device authentication.
        required: true
    username:
        description:
            - Username for device authentication.
        required: false
        default: "admin"
    category:
        description:
            - Category of file uploaded. The default is software.
        required: false
        default: software
    file:
        description:
            - Location of the file to import into device.
        required: false
        default: None
    url:
        description:
            - URL of the file that will be imported to device.
        required: false
        default: None
'''

EXAMPLES = '''
# import software image PanOS_vm-6.1.1 on 192.168.1.1
- name: import software image into PAN-OS
  panos_import:
    ip_address: 192.168.1.1
    username: admin
    password: admin
    file: /tmp/PanOS_vm-6.1.1
    category: software
'''

RETURN='''
# Default return values
'''

ANSIBLE_METADATA = {'metadata_version': '1.1',
                    'status': ['preview'],
                    'supported_by': 'community'}

from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import get_exception

import os.path
import xml.etree
import tempfile
import shutil
import os

try:
    import pan.xapi
    import requests
    import requests_toolbelt
    HAS_LIB = True
except ImportError:
    HAS_LIB = False


def import_file(xapi, module, ip_address, file_, category):
    xapi.keygen()

    params = {
        'type': 'import',
        'category': category,
        'key': xapi.api_key
    }

    filename = os.path.basename(file_)

    mef = requests_toolbelt.MultipartEncoder(
        fields={
            'file': (filename, open(file_, 'rb'), 'application/octet-stream')
        }
    )

    r = requests.post(
        'https://'+ip_address+'/api/',
        verify=False,
        params=params,
        headers={'Content-Type': mef.content_type},
        data=mef
    )

    # if something goes wrong just raise an exception
    r.raise_for_status()

    resp = xml.etree.ElementTree.fromstring(r.content)

    if resp.attrib['status'] == 'error':
        module.fail_json(msg=r.content)

    return True, filename


def download_file(url):
    r = requests.get(url, stream=True)
    fo = tempfile.NamedTemporaryFile(prefix='ai', delete=False)
    shutil.copyfileobj(r.raw, fo)
    fo.close()

    return fo.name


def delete_file(path):
    os.remove(path)


def main():
    argument_spec = dict(
        ip_address=dict(required=True),
        password=dict(required=True, no_log=True),
        username=dict(default='admin'),
        category=dict(default='software'),
        file=dict(),
        url=dict()
    )
    module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, required_one_of=[['file', 'url']])
    if not HAS_LIB:
        module.fail_json(msg='pan-python, requests, and requests_toolbelt are required for this module')

    ip_address = module.params["ip_address"]
    password = module.params["password"]
    username = module.params['username']

    xapi = pan.xapi.PanXapi(
        hostname=ip_address,
        api_username=username,
        api_password=password
    )

    file_ = module.params['file']
    url = module.params['url']

    category = module.params['category']

    # we can get file from URL or local storage
    if url is not None:
        file_ = download_file(url)

    try:
        changed, filename = import_file(xapi, module, ip_address, file_, category)
    except Exception:
        exc = get_exception()
        module.fail_json(msg=exc.message)

    # cleanup and delete file if local
    if url is not None:
        delete_file(file_)

    module.exit_json(changed=changed, filename=filename, msg="okey dokey")

if __name__ == '__main__':
    main()

On my inventory file (called hosts) I'm using:

Spoiler
#Palo Alto Firewall VM
[firewalls]
fwpa01 ansible_host=XXX.XXX.XXX.XXX ansible_user=testadmin

And on my vault:

Spoiler
---
vault_fwpa01_username: 'adminaccount'
vault_fwpa01_password: 'accountpassword'

You could give it a try with that and see if it works.

Also, I remember I used a dsa key on my firewall for authentication, but I think it is not used on this scenario.

 

 

 

  • 1 accepted solution
  • 3393 Views
  • 1 replies
  • 0 Likes
Like what you see?

Show your appreciation!

Click Like if a post is helpful to you or if you just want to show your support.

Click Accept as Solution to acknowledge that the answer to your question has been provided.

The button appears next to the replies on topics you’ve started. The member who gave the solution and all future visitors to this topic will appreciate it!

These simple actions take just seconds of your time, but go a long way in showing appreciation for community members and the LIVEcommunity as a whole!

The LIVEcommunity thanks you for your participation!