21. December 2017 · Comments Off on Using Ansible to manage ACL’s on Cisco IOS · Categories: Ansible, Cisco, Networking · Tags: , , , , , , , , ,

Finding the smartest way to broadly manage ACLs on Cisco Devices is always a cause of heartburn. In the past I have written ugly tcl/expect scripts to blindly push changes out to thousands of routers with little validation. Over time I started to get good at writing hackey checks to fake idempotentcy to prevent unneeded changes from being made. No tool is perfect, even using vendor tools like CSM or APIC-EM to manage ACLs can easily result in loss of communication to the target device. If not written properly Ansible can easily suffer similar shortcomings, although in Ansible’s case its likely your own fault for not testing properly.

The Coyote problem with Ansible

In my quest to find the least intrusive way to manage a consistent set of ACLs across all my Cisco devices I have yet to find any satisfactory playbooks. All, if not most, playbooks on github or blogs deal with access-lists by deleting and recreating the entire block. Even the ios_config docs page show examples of deleting target ACLs. Some of the fancier playbooks will go as far as de-referencing the ACL in the line, interface, or route-map before deleting it. Those approaches work but they are sub-optimal because when you delete an ACL you must be mindful of the following:

  • Routers pass live traffic, even during maintenance windows, deleting and recreating an ACL will interrupt traffic.
  • ACL’s tied to interfaces that are deleted without first removing the access-group from the interface will result no traffic passing; even management traffic.
  • Temporarily removing an access-group from an interface before deleting it will allow *all* traffic to pass.
  • Unfortunately the way configuration is done on Cisco devices there is not a straight forward way to commit all changes all at once, thereby taking a single quick hit like with carrier grade equipment. For example when making configuration changes its common to see services bouncing in and out of service each time you press the enter key. That being said the following is the most functionally and least intrusive solution I have been able to come up with. Its still *not* perfect! In this playbook I am still deleting unmatched sequence numbers that could potentially still be in use; only to re-add them on the next task item.

    ios_acl.yaml: Ansible executable playbook

    #!/usr/local/bin/ansible-playbook -f 10
    ---
    - name: ACL
      hosts: ios_lab
      gather_facts: false
      connection: local
      tags: [ "acl", "ios" ]
    
      vars_prompt:
    
      - name: "aclNAME"
        prompt: "ACL Name"
        private: no
        when: aclNAME is undefined
    
      vars:
    
      - aclLIST: "{{ ACL[aclNAME].LIST }}"
      - aclTYPE: "{{ ACL[aclNAME].TYPE }}"
    
      tasks:
    
      - name: "GET access-list"
        register: get_acl_config
        ios_command:
          provider: "{{ provider }}"
          commands:
            - "show access-lists {{ aclNAME }} | include ^\ +[1-9]"
    
      - name: "DEL access-list lines"
        when: "(get_acl_config.stdout_lines[0][0] != '') and (item not in lookup('template', 'ios_acl.j2'))"
        with_items: "{{ get_acl_config.stdout_lines[0] |\
                            regex_replace('[ \t]{2}') |\
                            regex_replace(', wildcard bits') |\
                            regex_replace(' [(].{9,30}[)]') }}"
        ios_config:
          provider: "{{ provider }}"
          lines: "no {{ item }}"
          parents: "ip access-list {{ aclTYPE }} {{ aclNAME }}"
        notify:
          - Save Configuration
    
      - name: "PUT access-list lines"
        when: "(item not in get_acl_config.stdout_lines[0] |\
                            regex_replace('[ \t]{2}') |\
                            regex_replace(', wildcard bits') |\
                            regex_replace(' [(].{9,30}[)]'))"
        with_items: "{{ lookup('template', 'ios_acl.j2').split('\n') }}"
        ios_config:
          provider: "{{ provider }}"
          lines: "{{ item }}"
          parents: "ip access-list {{ aclTYPE }} {{ aclNAME }}"
        notify:
          - Save Configuration
    
      handlers:
    
      - name: "Save Configuration"
        ios_command:
          provider: "{{ provider }}"
          commands: "write memory"

    group_vars/ios_lab.yaml: Ansible group variables

    ---
    provider:
      username: cisco
      password: cisco
      authorize: true
      auth_pass: cisco
      host: "{{ inventory_hostname }}"
      timeout: 120
    
    ACL:
      NETWORK_MANAGEMENT:
        TYPE: extended
        LIST:
          - permit ip 192.168.10.0 0.0.0.255 any
          - permit ip 192.168.20.0 0.0.0.255 any
          - permit ip 192.168.30.0 0.0.0.255 any
          - permit ip 192.168.40.0 0.0.0.255 any

    templates/ios_acl.j2: Jinja2 template

    {% for line in aclLIST %}{{ loop.index * 10 }} {{ line }}
    {% endfor %}