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_AWXSomething 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: allNext, 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: ipsAnd 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.