Ansible Networking - Check Point

Derived for Ansible 2.4

Ansible has been a blessing when trying to work with networking devices on a grand scale. Network and network security vendors are buying in to the new era of management backed by Ansible. This era is still in its infancy, but it is impossible to deny the momentum that is gaining in the field.

For all that momentum, however, there is still work to be done. For the number of disparate technologies that exist in the network and security field, not all vendors are stepping up and providing the management solutions their users are requesting. While I am confident in saying those vendors will be left in the dust when all is settled, until then there are opportunities to take advantage of their shortcomings.

Luckily, Ansible provides the resources to extend functionality in order for anyone to add capabilities. My initial goal was to extend Ansible to work with Check Point firewall technologies. In performing research for doing so, I found there to be a lack of information from a development standpoint for network security and Ansible in general. This will hopefully be able to cover the process of adding new functionality to Ansible by adding a new provider or using the network_cli command function added in version 2.5.

Barriers

Check Point firewalls run a Linux based operating system, but the shell has been obfuscated enough to the point where Ansible has a hard time running. Additionally, Python is not installed on Check Point and would be unsupported configuration if installed preventing the usual Linux focused Ansible modules from working. Most configuration for Check point is focused in a GUI and little API is exposed in versions R77 and lower (this is being improved in versions R80 and above).

Right off the bat, this limits the possibilities Ansible has out of the box for working with Check Point firewalls to using the 'raw' module or by executing commands in a sequential fashion. Because the 'raw' module does little error checking, I set out to create a framework in order to connect to the shell environment, elevate privilege, and execute commands safely.

Development Environment

Ansible provides a useful guide to create a development environment to make changes and test in. This consists of a Python virtual environment and setting other environment variables:

http://docs.ansible.com/ansible/latest/dev_guide/developing_modules_general.html

Developing a new networking module could very well be incorrect solution. If you are working with a standard shell environment, there is a good chance modules already exist for a majority of use cases.

Getting Started

Instead of starting from scratch, I'm going to begin with borrowing existing code. Looking through the codebase, Cisco's ASA modules look relatively similar. Cisco differs from Check Point in the following aspects which will need to be modified:

  • Cisco's proprietary shell environment and errors
  • Elevates privilege via 'enable' and 'conf t' while Check Point uses 'expert'
  • Structured configuration in 'enable' while Check Point's structured configuration is in it's unprivileged shell called 'clish'

With that in mind, let's look for the files needed to make the ASA modules work:

/ansible$ find . -name "asa.py"
./lib/ansible/utils/module_docs_fragments/asa.py
./lib/ansible/plugins/cliconf/asa.py
./lib/ansible/plugins/terminal/asa.py
./lib/ansible/plugins/action/asa.py
./lib/ansible/module_utils/asa.py

Looks like there are four pieces needed: action, terminal, cliconf, and module utilities.


Action

Functions for taking a playbook's action and beginning to run it. This will take the arguments like connection type, username, password, ssh port, and privilege escalation and begin the connection to the remote host.

Terminal

Functions for actions to take when a shell is opened, like disabling the terminal pager, and the actions to escalate privileges when 'authorize' is directed in a playbook.

Cliconf

Functions for running commands. Can restrict commands to being run only in privilege mode and can gather device info.

Module Utils

Functions for validating arguments passed to the module, setting up the connection to the remote host, and running commands.


These four form the basis of connecting to a Cisco ASA. Built on that foundation, are the modules themselves:

/ansible/lib/ansible/modules/network/asa$ ls
asa_acl.py  
asa_command.py  
asa_config.py  
__init__.py  

These modules provide the framework and abstraction for Ansible to:

  1. Create ACLs (asa_acl.py)
  2. Run commands (asa_command.py)
  3. Edit configuration blocks (asa_config.py)

Lastly, there is a base configuration file in Ansible listing the allowed network modules. If we want to add a new type, we should probably also add it here.

/ansible/lib/ansible/config$ cat base.yml | grep -A 4 NETWORK
NETWORK_GROUP_MODULES:  
  name: Network module families
  default: [eos, nxos, ios, iosxr, junos, ce, vyos, sros, dellos9, dellos10, dellos6, cp, asa, aruba, aireos]
  description: 'TODO: write it'
  env: [{name: NETWORK_GROUP_MODULES}]
  ini:
  - {key: network_group_modules, section: defaults}
  type: list
  yaml: {key: defaults.network_group_modules}

Developing Check Point Modules

Let's get to creating our module! To start out, I will focus on just being able to run commands in Check Point.

plugins/action/cp.py  
plugins/cliconf/cp.py  
plugins/terminal/cp.py  
module_utils/cp.py  
modules/network/cp/cp_command.py  
modules/network/cp/__init__.py  #initializes namespace
plugins/action/cp.py

Simply:
-- pc.network_os = 'asa'
++ pc.network_os = 'cp'

plugins/cliconf/cp.py

Cliconf primarily will be used to run commands using the 'get' function that calls the built in 'send_command' to remote hosts. We can borrow that ASA functionality directly. In cliconf, we can also add in Check Point specific device info calls in the function 'get_device_info'.

plugins/terminal/cp.py

In terminal lies some of the secret sauce to make this go. Terminal is responsible for recognizing the current shell environment ('>' or '#') and sending the input needed to elevate privilege.

Modifiy regex which checks for error code responses:

-- re.compile(br"Removing.* not allowed, it is being used")
++ re.compile(r"Invalid", re.I),
++ re.compile(r"command not found", re.I),
++ re.compile(r"usage:", re.I)

Substitute the privilege escalation command 'enable' with 'expert' to be run in the remote shell:

-- cmd = {u'command': u'enable'}
++ cmd = {u'command': u'expert'}

Modify the check for the privilege escalation prompt:

-- cmd[u'prompt'] = to_text(r"[\r\n]?password: $", errors='surrogate_or_strict')
++ cmd[u'prompt'] = to_text(r"[\r\n]?Enter expert password:$", errors='surrogate_or_strict')

These are probably the most important changes that need to be made. Other slight changes such as disabling the terminal pager are also included in this file.

module_utils/cp.py

The module utility checks the arguments passed in the playbook and contains validation prior to passing to the plugin files listed above. The main modification I wanted to change, aside from refactoring the function names from 'asa' to 'cp', is adding a check to prevent 'show' commands from running when privilege is being escalated from 'clish' to 'expert' mode.

++ if item['command'].startswith('show') and cp_argument_spec.get('authorize'):
module.fail_json(msg='show commands are not supported when using authorize')

modules/network/cp/cp_command.py

The named module to use in your playbooks. This will take arguments, pass them into actions to be executed and will return the output. This is nearly like for like with Cisco's module.

Running Playbooks

Now that we have everything in place, we can create a playbook and run it!

---
- name: run cp commands
  hosts: cp
  gather_facts: False
  connection: local
  vars:
    cli:
      host: "{{ inventory_hostname }}"
      username: admin
      password: password
      authorize: yes
      auth_pass: password
      timeout: 300
  tasks:
    - name: get firewall version
      cp_command:
        commands:
          - fw ver
        provider: "{{ cli }}"
      regsiter: result
    - debug: msg="{{ result.stdout }}"

Result excerpt:

{
    "hosts": {
        "10.13.0.50": {
            "_ansible_no_log": false, 
            "_ansible_verbose_always": true, 
            "changed": false, 
            "failed": false, 
            "msg": [
                "This is Check Point's software version R77.10 - Build 243"
            ]
    }
}, 

Wrap Up

Code is located in my personal fork under the 'cp' branch: https://github.com/tuckner/ansible/tree/cp. Hope this inspires some other uniquely odd use case!

John Tuckner

Read more posts by this author.