Ansible survey foreign EdgeMAX

As we've been improving our systems automation with ansible, that has included bringing 'foreign' devices with unknown states into consistency and management by ansible. This will look at some tricks used to survey and update EdgeMAX routers.

First things first is having some idea of what types of equipment are being captured by automation, and for what purpose. In our case, we had edgemax routers doing two primary functions: port-forwarding remote access for appliances, and WAN routing two public subnet blocks. We wanted to bring them into management with ansible for the primary purpose of updating firewall rules allowing access to the site. Knowing what to look for and what we want to update will inform our survey process.

Because the routers were in an inconsistent state and some had different access requirements, the first thing we needed to do was check whether ansible needed to use a bastion host to access the site. This demanded a bastion host to be setup and the credentials established for the ansible client performing the survey (generally our AWX task runner), but those details aren't covered in this post.

- name: Jumphost rescue
  block:
    - name: Connection test (will rescue failures with jumphost)
      edgeos_config:
        lines:
          # The name should be sufficiently unique to AWX in order to
          # avoid conflict with any pre-existing address-group
          - set firewall group address-group AWXTrustedSrc description MANAGED_BY_AWX
  rescue:
    - name: use jumphost to access router
      delegate_to: localhost
      set_fact:
        ansible_ssh_common_args: '-o ProxyCommand="ssh -W %h:%p -q {{bastion}}" '
        # authentication to bastion host established in SSH config of AWX taskrunner.
    - name: Connection test (via jumphost)
      edgeos_config:
        lines:
          - set firewall group address-group AWXTrustedSrc description MANAGED_BY_AWX

Something to note is that the descriptions are going to be single word strings rather than quoted strings to reduce false-positives of system changes designated by the the edgeos_config module. Furthermore, set_fact is generally delegated to localhost to avoid connection errors. For more, see: delegated_facts.

With access sorted, ansible can proceed to survey the router. First gather the general facts.

- name: collect all facts from the device
  edgeos_facts:
    gather_subset: all

Next, gather port forwarding information, which is the preferred method for identifying the WAN and LAN interfaces.

- name: Port forward block
  block:
    - name: get port forward settings
      edgeos_command:
        commands: show configuration commands port-forward
      register: erpinhole
    - set_fact:
        lanLine: "{{ lookup('flattened', erpinhole.stdout_lines[0] |select('match', 'set port-forward lan-interface (.+)') |list) }}"
        wanLine: "{{ lookup('flattened', erpinhole.stdout_lines[0] |select('match', 'set port-forward wan-interface (.+)') |list) }}"
      delegate_to: localhost
    - set_fact:
        lanIF  : "{{ lanLine |trim |regex_replace('set port-forward lan-interface (.+)', '\\1') }}"
        wanIF  : "{{ wanLine |trim |regex_replace('set port-forward wan-interface (.+)', '\\1') }}"
      when:
        - wanLine|default('') != ''
        - lanLine|default('') != ''
      delegate_to: localhost
  rescue:
    - debug:
        msg: Disregard the previous error. Was checking for non-essential settings.

If the router didn't have port forwarding interfaces configured, the alternative method for identifying the WAN interface is to check the default route.

- name: alternate get WAN interface
  when: wanIF|default([])|length|int == 0
  block:
    - edgeos_command:
        commands: ip route |grep default
      register: route
    - set_fact:
        wanIF: "{{ route.stdout |regex_replace('.* dev (.+?) .*', '\\1') }}"
      delegate_to: localhost

The next part of the survey is to gather the firewall settings and the IP addresses. We'll need the IPs because we want to count how many public IPs are on the router. In our case, we'll apply different firewall rules when a router has multiple public IPs vs when it has a single public IP.

- name: get firewall settings
  edgeos_command:
    commands: show configuration commands firewall
  register: firewall
- name: get IPs
  edgeos_command:
    commands: ip addr |grep 'inet ' |awk '{$1=$1};1' |cut -f2 -d' '
  register: ips

And finally, we can declare some host facts which will be used when updating the router settings.

- set_fact:
    hostname: "{{ansible_net_hostname}}"
    lanIF: "{{lookup('flattened', lanIF)}}"
    wanIF: "{{lookup('flattened', wanIF)}}"
    model: "{{ansible_net_model}}"
    version: "{{ansible_net_version}}"
    firewallLines: "{{ firewall.stdout_lines[0] |length|int }}"
    numPublic: "{{ ips.stdout_lines[0] |ipaddr('public') |length|int }}"
  delegate_to: localhost

With those facts declared, we can proceed to update the router. Certainly your circumstances and requirements may differ from ours, but this is an interesting concept nonetheless – using ansible to survey an appliance in an unknown state in preparation for bringing it into a consistent state and inventory for future management.