Ansible

Eliminate Cisco CLI Repetition

Network device CLI configuration is one thing that I fell in love with as I started my journey into IT Security. While ...


Network device CLI configuration is one thing that I fell in love with as I started my journey into IT Security. While not really "coding" it just gives the feel of programming a system to jobs and handle commands. But as I started working on larger and larger projects my love for the CLI turned into more of a copy-paste operation as the networks I worked on got larger and larger. It really started to feel like Daft Punk's "Around the World" in CLI form.

Back again with Ansible

Cisco IOS Ansible Example

As you may already know I am a fan of Ansible for many different use cases like FMC or ISE. So it should come as no surprise that I also like to use it for traditional Cisco network device configuration as well. Just like the FMC and ISE Ansible Collections, there are multiple modules for managing and configuring Cisco IOS devices including a general command and configuration module which enables pretty much any command to be run on any IOS device.

Installing the collection on your system is no different than any other collection install and uses Ansible Galaxy with the following command

ansible-galaxy collection install cisco.ios

100's of Lines

If you have ever deployed something like 802.1X or just general AAA for device administration you are most likely an avid user of Notepad and copy/paste keyboard shortcuts. Thankfully, many of the configurations are the same across all of the devices and can be templated rather easily. With that in mind, using Ansible is the perfect solution to make these repetitive tasks go by rather quickly without the need to SSH to every device and hit paste a few hundred times.

As an example, the 802.1X policy map and interface template is about 60 lines of static configuration that needs to exist on all switches in the network that will authenticate users and devices onto the network. Using Ansible with the IOS collection we will easily apply this configuration and many more lines of configuration to multiple devices.

Ansible Hosts and Variables

First we will start with the hosts file which tells Ansible what devices to connect to and any specifics the code might need like variables and login information. As you can see there are variables that can be defined at the host-specific level and to the group level.

switches:
hosts:
10.99.254.30:
mgmt_loop_num: 255
mgmt_loop: 10.3.3.3
mgmt_mask: 255.255.255.255
access_vlan: 100
voice_vlan: 200
10.99.254.31:
mgmt_loop_num: 255
mgmt_loop: 10.4.4.4
mgmt_mask: 255.255.255.255
access_vlan: 300
voice_vlan: 400
vars:
radius_server_group: ise-radius-group
radius_servers:
- name: ise01
ip: 10.99.10.123
secret: cisco123
authPort: 1812
acctPort: 1813
- name: ise02
ip: 10.99.10.124
secret: cisco123
authPort: 1812
acctPort: 1813
ansible_network_os: cisco.ios.ios
ansible_user: cisco
ansible_password: cisco123
ansible_become: yes
ansible_become_method: enable
ansible_become_password: cisco123
ansible_connection: ansible.netcommon.network_cli

IOS Command and Config Modules

Now that we have the host file sorted we can start building the playbook to execute the configuration items that we desire. The IOS collection has multiple specific modules for configuration like ACLs, BGP, NTP, and many other services. Those modules certainly have thier use cases but since I have been using the CLI for so long that I find it easier to use the command and config modules for most of my playbooks.

The following example shows how to use the command module to run show commands and register the response as a variable for later use.

 - name: Verify Authentication Config Mode for IBNS 2.0
cisco.ios.ios_command:
commands:
- authentication display config-mode
register: configMode

After registering the variable from the show command we can then have to use the Ansible Netcommon CLI command with a conditional if the current setting is not the desired output. The reason for this is that the command and config IOS modules do not handle prompts well if at all from what I could find.

 - name: Enable IBNS 2.0 Configuration Display
ansible.netcommon.cli_command:
command: "{{item}}"
prompt: continue
answer: 'yes'
with_items:
- configure terminal
- authentication convert-to new-style
when: configMode.stdout_lines is search("legacy")

After making sure the devices are set up to receive IBNS 2.0 based configuration we can start to use the config module to push configurations to the devices. As usual with Ansible we can use the variables in the hosts file or a dedicated variable file. In the example below we use the group variables for the Radius servers and you can see that the variables are inserted into the standard CLI syntax that would normally be used.

 - name: Configure Radius Servers
cisco.ios.ios_config:
lines:
- address ipv4 {{item.ip}} auth-port {{item.authPort}} acct-port {{item.acctPort}}
- timeout 4
- retransmit 3
- key 0 {{item.secret}}
parents: radius server {{item.name}}
with_items:
- ''

For more static types of configuration like the Auth Policies, Class Maps, or ErrDisable settings we can also leverage the Jinja syntax templates and simply reference them in the Ansible config module.

Here is an example of a Jinja template named ibns2-radius-attributes.j2.

radius-server attribute 6 on-for-login-auth
radius-server attribute 6 support-multiple
radius-server attribute 8 include-in-access-req
radius-server attribute 25 access-request include
radius-server attribute 31 mac format ietf upper-case
radius-server attribute 31 send nas-port-detail mac-only
radius-server dead-criteria time 5 tries 3
radius-server deadtime 3

We can apply that template with the src keyword in the Ansible code as seen below.

 - name: Apply Radius Server Attributes Jinja
cisco.ios.ios_config:
src: ibns2-radius-attributes.j2

Applying configuration to multiple interfaces is as simple as using either a range command in the parents: operator or a with_items: and the ports to configure in the playbook.

 - name: Configure Access Ports
cisco.ios.ios_config:
lines:
- switchport access vlan
- switchport voice vlan
- source template DefaultWiredDot1xClosedAuth
- spanning-tree portfast
- spanning-tree bpduguard enable
parents: '{{item}}'
with_items:
- interface gigabitethernet0/2
- interface gigabitethernet0/3

Finally, we would want to save the configurations.

 - name: Save Configuration
cisco.ios.ios_config:
save_when: modified

Concluding Playbook

Gone are the days of configuring via SSH and copy/paste with the power of Ansible. If you are wondering this all can be, and was tested using Cisco Modeling Labs with external connectors in my home lab. If you want to read more about CML check this blog out. Happy coding!

If you want to see the full code example see our public GitHub Repo here

ModernCyber’s Services

If you are looking for help with automation or deployment services using a proven methodology with consistent results let us know. We are actively working on multiple projects where infrastructure as code is used and an invaluable asset to getting success with your missions.

Schedule some time to speak with one of our cybersecurity experts.