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.

    Similar posts