Note

This tutorial uses the syntax required as of version 2.0. Please see the HTTPAPI Plugin example for details.

Tutorial v2.x

This section is intended to help users who may be new to Ansible get up and running with their first playbook using the Sophos Firewall Ansible Collection. Experienced Ansible users may want to skip this section and review the documentation for the individual modules.

Ansible requires two main components to operate; an inventory and a playbook. An inventory file specifies what firewall(s) the Ansible playbook should operate on. It can also include variable definitions that can be referenced in the playbook.

Inventory

Below is an inventory file containing a single firewall with variable definitions needed for connectivity. The ansible_httpapi_validate_certs: false setting turns off certificate checking. This should be set to true if the firewall has a valid SSL certificate installed.

# inventory.yml
all:
  hosts:
    testfirewall:
      ansible_host: [firewall_ip_or_hostname]
  vars:
    ansible_user: "{{ firewall_username }}"
    ansible_password: "{{ firewall_password }}"
    ansible_connection: ansible.netcommon.httpapi
    ansible_httpapi_validate_certs: false
    ansible_httpapi_port: 4444
    ansible_network_os: sophos.sophos_firewall.sfos

Credentials

The ansible_user and ansible_password arguments require special consideration to ensure the credentials are stored securely. While the credentials could be entered directly in the inventory, doing so is highly discouraged because these files are often checked into a source control repository such as Github where they can be easily read by anyone. There are many options to supply credentials securely to Ansible. One of the built-in options is using Ansible Vault. Ansible Vault can be used to encrypt the credentials in a file, and decrypt the file at run time to retrieve the variables. Other options include using a secrets manager, such as Hashicorp Vault. For this guide, we’ll use Ansible Vault.

First, create a YAML file containing the username and password variables.

# credentials.yml
firewall_username: <your firewall username>
firewall_password: <your firewall password>

Create a directory group_vars and within that a subdirectory all. Save the file as group_vars/all/credentials.yml. Next, use the ansible-vault command to encrypt the file with AES 256-bit encryption:

$ ansible-vault encrypt group_vars/all/credentials.yml

The above command will prompt for creation of an encryption password. This password will be entered at the command line to decrypt the credentials when running the playbook.

Now when we run the playbook, Ansible will replace the {{ firewall_username }} and {{ firewall_password }} in the inventory file with the values from credentials.yml.

Playbook

A playbook file contains tasks that will be executed against the firewall(s) in the inventory file. At the top of the file, the inventory group is specified. In this case, all is used to specify operation on all hosts in the inventory. The gather_facts: false command disables Ansible from gathering details about the system that it is working on because it is not necessary for this collection and would make the playbook run more slowly.

In the tasks section of the playbook, a module is referenced using the format {namespace}.{collection}.{module}. In the below example, the module sophos.sophos_firewall.sfos_ip_host will be used to create an IP Host on Sophos firewall.

# playbook.yml
---
- name: SOPHOS FIREWALL ANSIBLE PLAYBOOK
  hosts: all
  gather_facts: false

  tasks:
    - name: ADD IP HOST
      sophos.sophos_firewall.sfos_ip_host:
        name: testhost1
        ip_address: 1.1.1.1
        state: present

In the above task, underneath the module name sophos.sophos_firewall.sfos_ip_host are the arguments needed to configure the IP host. For each module, there will be arguments that are specific to the module, such as name and ip_address in the above example.

Refer to the individual module documentation for the available arguments for each module. The state argument indicates the operation to be performed. Modules support the following values for the state argument:

  • present: Create if not existing

  • absent: Delete if exists

  • updated: Update existing

  • query: Retrieve the existing

To run the playbook, use the ansible-playbook command as shown below:

$ ansible-playbook -i inventory.yml playbook.yml --ask-vault-pass -v

When prompted, enter the encryption password created when executing the ansible-vault command above. Output should look similar to the following:

$ ansible-playbook -i inventory.yml test.yml --ask-vault-pass -v
No config file found; using defaults
Vault password:

PLAY [SOPHOS FIREWALL ANSIBLE PLAYBOOK] *********************************************************************************************************

TASK [ADD IP HOST] ************************************************************************************************************************************
changed: [testfirewall.sophos.com -> localhost] => {"api_response": {"Response": {"@APIVersion": "2000.2", "@IPS_CAT_VER": "1", "@IS_WIFI6": "0", "IPHost": {"@transactionid": "", "Status": {"#text": "Configuration applied successfully.", "@code": "200"}}, "Login": {"status": "Authentication Successful"}}}, "changed": true, "check_mode": false}

PLAY RECAP ********************************************************************************************************************************************
testfirewall.sophos.com              : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Note

The modules in this collection are idempotent; they will not take any action if the configuration is already in the intended state. Due to this, if you run the above task a second time, the response should indicate changed=0.

To check that the host was created, you can check in the firewall dashboard under System > Hosts and services > IP host. To check using an Ansible task, change the state argument to query.

# playbook.yml
---
- name: SOPHOS FIREWALL ANSIBLE PLAYBOOK
  hosts: all
  gather_facts: false

  tasks:
    - name: QUERY IP HOST
      sophos.sophos_firewall.sfos_ip_host:
        name: testhost
        state: query

The output should be similar to the following:

$ ansible-playbook -i inventory.yml test.yml --ask-vault-pass -v
No config file found; using defaults
Vault password:

PLAY [SOPHOS FIREWALL ANSIBLE PLAYBOOK] *********************************************************************************************************

TASK [QUERY IP HOST] **********************************************************************************************************************************
ok: [testhost.sophos.com -> localhost] => {"api_response": {"Response": {"@APIVersion": "2000.2", "@IPS_CAT_VER": "1", "@IS_WIFI6": "0", "IPHost": {"@transactionid": "", "Description": null, "HostType": "IP", "IPAddress": "1.1.1.1", "IPFamily": "IPv4", "Name": "testhost"}, "Login": {"status": "Authentication Successful"}}}, "changed": false, "check_mode": false}

PLAY RECAP ********************************************************************************************************************************************
testhost.sophos.com              : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

To update the test host configuration, such as change the IP address, we can change the ip_address argument and set the state field to updated:

# playbook.yml
---
- name: SOPHOS FIREWALL ANSIBLE PLAYBOOK
  hosts: all
  gather_facts: false

  tasks:
    - name: UPDATE IP HOST
      sophos.sophos_firewall.sfos_ip_host:
        name: testhost
        ip_address: 2.2.2.2
        state: updated
$ ansible-playbook -i inventory.yml test.yml --ask-vault-pass -v
No config file found; using defaults
Vault password:

PLAY [SOPHOS FIREWALL ANSIBLE MODULE TESTING] *********************************************************************************************************

TASK [UPDATE IP HOST] *********************************************************************************************************************************
changed: [testhost.sophos.com -> localhost] => {"api_response": {"Response": {"@APIVersion": "2000.2", "@IPS_CAT_VER": "1", "@IS_WIFI6": "0", "IPHost": {"@transactionid": "", "Status": {"#text": "Configuration applied successfully.", "@code": "200"}}, "Login": {"status": "Authentication Successful"}}}, "changed": true, "check_mode": false}

PLAY RECAP ********************************************************************************************************************************************
testhost.sophos.com              : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

To see that the IP address has changed, we can change the state again to query, and this time register a variable to store the result using the register module argument. Then a second task is added using the built-in debug module to display the ip address.

# playbook.yml
---
- name: SOPHOS FIREWALL ANSIBLE PLAYBOOK
  hosts: all
  gather_facts: false

  tasks:
    - name: QUERY IP HOST
      sophos.sophos_firewall.sfos_ip_host:
        name: testhost
        state: query
      # Added a variable called query_host to store the results of the task
      register: query_host

    - name: DISPLAY IP ADDRESS
      ansible.builtin.debug:
        var: query_host.api_response.Response.IPHost.IPAddress

The output should look similar to the following:

$ ansible-playbook -i inventory.yml test.yml --ask-vault-pass -v
No config file found; using defaults
Vault password:

PLAY [SOPHOS FIREWALL ANSIBLE PLAYBOOK] *********************************************************************************************************

TASK [UPDATE IP HOST] *********************************************************************************************************************************
ok: [testhost.sophos.com -> localhost] => {"api_response": {"Response": {"@APIVersion": "2000.2", "@IPS_CAT_VER": "1", "@IS_WIFI6": "0", "IPHost": {"@transactionid": "", "Description": null, "HostType": "IP", "IPAddress": "2.2.2.2", "IPFamily": "IPv4", "Name": "testhost"}, "Login": {"status": "Authentication Successful"}}}, "changed": false, "check_mode": false}

TASK [DISPLAY IP ADDRESS] *****************************************************************************************************************************
ok: [testhost.sophos.com] => {
    "query_host.api_response.Response.IPHost.IPAddress": "2.2.2.2"
\}

PLAY RECAP ********************************************************************************************************************************************
testhost.sophos.com              : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Finally, if we want to delete the IP Host we can set the state to absent.

# playbook.yml
---
- name: SOPHOS FIREWALL ANSIBLE PLAYBOOK
  hosts: all
  gather_facts: false

  tasks:
    - name: REMOVE IP HOST
      sophos.sophos_firewall.sfos_ip_host:
        name: testhost
        state: absent
$ ansible-playbook -i inventory.yml test.yml --ask-vault-pass -v
No config file found; using defaults
Vault password:

PLAY [SOPHOS FIREWALL ANSIBLE PLAYBOOK] *********************************************************************************************************

TASK [REMOVE IP HOST] *********************************************************************************************************************************
changed: [testhost.sophos.com -> localhost] => {"api_response": {"Response": {"@APIVersion": "2000.2", "@IPS_CAT_VER": "1", "@IS_WIFI6": "0", "IPHost": {"@transactionid": "", "Status": {"#text": "Configuration applied successfully.", "@code": "200"}}, "Login": {"status": "Authentication Successful"}}}, "changed": true, "check_mode": false}

PLAY RECAP ********************************************************************************************************************************************
testhost.sophos.com              : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Now if we change the state argument back to query, we should see a “No. of records Zero.” response which confirms the IP Host was deleted.

$ ansible-playbook -i inventory.yml test.yml --ask-vault-pass -v
No config file found; using defaults
Vault password:

PLAY [SOPHOS FIREWALL ANSIBLE PLAYBOOK] *********************************************************************************************************

TASK [QUERY IP HOST] **********************************************************************************************************************************
ok: [testhost.sophos.com -> localhost] => {"api_response": "No. of records Zero.", "changed": false, "check_mode": false}

PLAY RECAP ********************************************************************************************************************************************
testhost.sophos.com              : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

For more playbook task examples, see the Examples section in each of the individual modules.

Warning

If the firewall is a member of a group in Central, the changes made by Ansible will override the settings of the group. The actual configuration on the firewall may then differ from what is displayed for the same setting in the Central group.