ASAs and Ansible: Variables and APIs

Following on from the post about using Ansible to manage the 100+ Pingdom probe IP addresses, today’s post is very similar, but this time working with the IP addresses for the Palo Alto Global Protect Cloud Service (GPCS). To give the background and reasoning for this, when you use GPCS, you get a bunch of VMs around the globe that you could end up connected to when using the GlobalProtect VPN.

If you need to get a list of these then you can do an API call, after generating an API key in the Panorama server and doing a “curl” on a web address. These addresses can change, so it’s important to keep them updated. You can read more about it on the Palo Alto website.

The ansible script from yesterday gave me an idea about how I could use it for this as well. This one is a bit harder as we need to pass and uses variables, in this case, the API key.

Here it is.

UpdateGPCSIPs.yml:

ansible@ansible ~$ cat UpdateGPCSIPs.yml
- hosts: Cisco_ASAs
  connection: local
  tasks:
   - name: GET CREDS
     include_vars: secrets.yml
   - name: DEFINE CONNECTION
     set_fact:
        connection:
           authorize: yes
           host: "{{ inventory_hostname }}"
           username: "{{ creds['username'] }}"
           password: "{{ creds['password'] }}"
           auth_pass: "{{ creds['auth_pass'] }}"
           timeout: 30

   - shell: "curl -k -H header-api-key:{{api_key}} \"https://api.gpcloudservice.com/getAddrList/latest?fwType=$fwType&addrType=$addrType\" > /tmp/GPCS-IPs.txt"
   - replace:
      path: /tmp/GPCS-IPs.txt
      regexp: '", "'
      replace: "\n"
   - replace:
      path: /tmp/GPCS-IPs.txt
      regexp: '\[\"'
      replace: ''
   - replace:
      path: /tmp/GPCS-IPs.txt
      regexp: '\"\]'
      replace: "\n"
   - replace:
      path: /tmp/GPCS-IPs.txt
      regexp: 'total-count\"\: 44, \"addrList\"\: '
      replace: ''
   - lineinfile:
      dest: /tmp/GPCS-IPs.txt
      regexp: '\{\"status\"\: \"success'
      state: absent
   - lineinfile:
      dest: /tmp/GPCS-IPs.txt
      regexp: 'result\"\: \{\"fwType\"\: \"all'
      state: absent
   - lineinfile:
      dest: /tmp/GPCS-IPs.txt
      regexp: 'addrListType\"\: \"all'
      state: absent
   - lineinfile:
      dest: /tmp/GPCS-IPs.txt
      regexp: '^DC03|^DC04|^DC02|^DC01|10.109|Dev-AWS|APAC'
      state: absent
   - lineinfile:
      path: /tmp/GPCS-IPs.txt
      regexp: '\}\}'
      state: absent

   -  shell: cat /tmp/GPCS-IPs.txt | awk 'BEGIN { FS=":" } {print $2}' > /tmp/GPCS-IPs-only.txt
      register: IPs

   - asa_config:
       lines:
         - network-object host {{ item }}
       parents: ['object-group network GPCS-IPs']
       provider: "{{ connection }}"
     with_lines: cat /tmp/GPCS-IPs-only.txt
     register: result

   - debug: var=result
   - asa_command:
       commands:
         - wr mem
       provider: "{{ connection }}"

A lot of will be familiar if you have read the previous post. Below I will explain the pertinent changes.

- shell: "curl -k -H header-api-key:{{api_key}} \"https://api.gpcloudservice.com/getAddrList/latest?fwType=$fwType&addrType=$addrType\" > /tmp/GPCS-IPs.txt"

This calls the web service, using the api_key variable, which is passed in the curly braces: {{api_key}}. We send the output to a file called GPCS-IPs.txt in the /tmp directory.

   - replace:
      path: /tmp/GPCS-IPs.txt
      regexp: 'total-count\"\: 44, \"addrList\"\: '
      replace: ''
   - lineinfile:
      dest: /tmp/GPCS-IPs.txt
      regexp: '\{\"status\"\: \"success'
      state: absent
   - lineinfile:
      dest: /tmp/GPCS-IPs.txt
      regexp: 'result\"\: \{\"fwType\"\: \"all'
      state: absent
   - lineinfile:
      dest: /tmp/GPCS-IPs.txt
      regexp: 'addrListType\"\: \"all'
      state: absent

We then have a bunch of replaces and lineinfile commands. This tidies up the output so that it is formatted as a nice list. The lineinfile command is useful when you want to remove a line, as the replace command will leave you with white lines. Using the state of “absent”, it removes the line, or lines, matched by the regex.

Because the output includes a lot of IP addresses that are local to your environment, as well as any remote networks VPN endpoints, these also need  to be stripped out, so that you end up with just the external IP addresses within the AWS network:

   - lineinfile:
      dest: /tmp/GPCS-IPs.txt
      regexp: '^DC03|^DC04|^DC02|^DC01|10.109|Dev-AWS|APAC'
      state: absent
   - lineinfile:
      path: /tmp/GPCS-IPs.txt
      regexp: '\}\}'
      state: absent

What we get at the end of the replaces and lineinfile statements is a list of locations and IP addresses, separated by a colon (:). To get just the IP addresses, we need to revert to some good old cat’ing and awk’ing:

   -  shell: cat /tmp/GPCS-IPs.txt | awk 'BEGIN { FS=":" } {print $2}' > /tmp/GPCS-IPs-only.txt
      register: IPs

I probably don’t need to register a result here, but what the hell.

The last section of the playbook, is to create the access-group along with the IP addresses we now have:

   - asa_config:
       lines:
         - network-object host {{ item }}
       parents: ['object-group network GPCS-IPs']
       provider: "{{ connection }}"
     with_lines: cat /tmp/GPCS-IPs-only.txt
     register: result

   - debug: var=result
   - asa_command:
       commands:
         - wr mem
       provider: "{{ connection }}"

We call the script using the command:

ansible-playbook UpdateGPCSIPs.yml -i hosts -e api_key=myapikeywhichisaweirdlookingstring

The “-e” is the same as using –extra-vars. We can tag as many variables as we want to, just remember to enclose multiple variables in brackets (-e “var1=hellow var2=world”).

So far, Ansible is proving a lot of fun. However, the scripts, as they stand so far would get me a big slap on the wrist from our PCI auditor, so we’ll fix that in the next post, and then after that, we’ll start creating some ACLs using the new access-groups that have been created!