Creating an ACL template using Ansible

My goal here was to make updating an ACL that is applied on multiple devices as easy as possible. Below is a portion of my vars.yml file just to give an idea of how updates are done:

- security_scanner: &ss
     name: "SS1"
     ip: "192.168.199.3"

running the playbook produces the following:
....
 ip access-list extended printer_acl_in
   permit ip 192.168.1.0 0.0.0.255 host 192.168.199.3
.....
!
 ip access-list extended print_acl_out
   permit ip host 192.168.199.3 192.168.1.0 0.0.0.255
.....

In this case i’m creating a printer subnet acl. Let’s say the security scanning folks changed the IP of their scanner from 199.3 to 199.4. Now, all you have to do is update that in the vars.yml file, and then re-create the config files for the devices which you can then push out. I’ll go ahead and update the IP to 199.4, and then create the configs.

- security_scanner: &ss
     name: "SS1"
     ip: "192.168.199.4"
!
BOX$ ansible-playbook playbook.yml
.....
PLAY RECAP *******************************************************************************************************************************
SWITCH1                    : ok=1    changed=1    unreachable=0    failed=0
.....
The config files now show:
.....
 ip access-list extended printer_acl_in
   permit ip 192.168.1.0 0.0.0.255 host 192.168.199.4
.....
!
 ip access-list extended print_acl_out
   permit ip host 192.168.199.4 192.168.1.0 0.0.0.255
.....

Now I’ll show the full vars.yml file which includes variables for all of the access switches. This could easily be updated to include the full configuration for the devices, but I just limited it to the basics to show the ACL portion.

One of the new things I learned about when doing this was the use of anchors. This allows you to remove duplication in the file by referencing a single spot. You can see these below for the domain_controllers and security_scanner. The &[name] creates the anchor point, and you then refer back to it via “<<: *[name]”. This allows you to copy all of the items in the anchor point to any other location in the file. Under the printer_acl section, you can see I included the anchor references under “pieces” which means both “printer_acl_in” and “printer_acl_out” now have copies of those variables which i then use later to build the acls.

- domain_controller1 : &dc1
     name: "DC1"
     ip: "192.168.199.1"

- domain_controller2 : &dc2
     name: "DC2"
     ip: "192.168.199.2"

- security_scanner: &ss
     name: "SS1"
     ip: "192.168.199.4"


- printer_acl : &printer_acl
    acls:
      { acl_in: [
          { name: "printer_acl_in", pieces: [ <<: *ss, <<: *dc1, <<: *dc2 ] }
        ],
        acl_out: [
          { name: "print_acl_out", pieces: [ <<: *ss, <<: *dc1, <<: *dc2 ] }
        ]
      }

- access_switches:
        switch1 :
          { type: "switch" ,
            hostname: "SWITCH1",
            version: "12.2",
            interfaces: [
              { name: "Ethernet0/0",
                desc: "User Subnet",
                address: "192.168.0.1",
                network: "192.168.0.0",
                mask: "255.255.255.0",
                wild: "0.0.0.255",
                type: "user"
              },
              { name: "Ethernet0/1",
                desc: "Printer Subnet",
                address: "192.168.1.1",
                network: "192.168.1.0",
                mask: "255.255.255.0",
                wild: "0.0.0.255",
                type: "printer",
                <<: *printer_acl
              }
            ]
          }
        switch2 :
          { type: "switch",
            hostname: "SWITCH2",
            version: "12.2",
            interfaces: [
              { name: "Ethernet0/0",
                desc: "User Subnet",
                address: "192.168.0.2",
                network: "192.168.0.0",
                mask: "255.255.255.0",
                wild: "0.0.0.255",
                type: "user"
              },
              { name: "Ethernet0/1",
                desc: "Printer Subnet",
                address: "192.168.1.2",
                network: "192.168.1.0",
                mask: "255.255.255.0",
                wild: "0.0.0.255",
                type: "printer",
                <<: *printer_acl
              }
            ]
          }

The playbook grabs the vars.yml file, and then runs it against the template:
- name:  test
  connection: local
  gather_facts: none
  hosts: SWITCH1
  vars_files:
          - "vars.yml"
  tasks:
          - template: src=./template.j2 dest=./CONFIGS/{{item.value.hostname}}.cfg
            with_dict: "{{ access_switches }}"



and the template file that generates the final configs: The template.j2 file
  hostname: {{ item.value.hostname }}
  version: {{ item.value.version }}
!
{# Building ACLs needed first #}
{# Need to loop through the interfaces to find acls that are needed #}
{% for int in item.value.interfaces %}
{% if int.acls is defined %}
{# Checking to see if an INBOUND acl is needed #}
{% for acl in int.acls.acl_in %}
 ip access-list extended {{ acl.name }}
{% for pieces in acl.pieces %}
   permit ip {{ int.network }} {{ int.wild }} host {{ pieces.ip }}
{% endfor %}
   exit
{% endfor %}
!
{# Checking to see if an OUTBOUND acl is needed #}
{% for acl in int.acls.acl_out %}
 ip access-list extended {{ acl.name }}
{% for pieces in acl.pieces %}
   permit ip host {{ pieces.ip }} {{ int.network }} {{ int.wild }}
{% endfor %}
   exit
{% endfor %}
{% endif %}
{% endfor %}
!
{% for int in item.value.interfaces %}
  interface {{ int.name }}
    ip address {{ int.address }} {{ int.mask }}
{% if int.acls is defined %}
{% for acl in int.acls.acl_in %}
    ip access-group {{ acl.name }} in
{% endfor %}
{% for acl in int.acls.acl_in %}
    ip access-group {{ acl.name }} out
{% endfor %}
{% endif %}
!
{% endfor %}

Here are the full final configs that are generated:

SWITCH1.cfg
  hostname: SWITCH1
  version: 12.2
!
 ip access-list extended printer_acl_in
   permit ip 192.168.1.0 0.0.0.255 host 192.168.199.4
   permit ip 192.168.1.0 0.0.0.255 host 192.168.199.1
   permit ip 192.168.1.0 0.0.0.255 host 192.168.199.2
   exit
!
 ip access-list extended print_acl_out
   permit ip host 192.168.199.4 192.168.1.0 0.0.0.255
   permit ip host 192.168.199.1 192.168.1.0 0.0.0.255
   permit ip host 192.168.199.2 192.168.1.0 0.0.0.255
   exit
!
  interface Ethernet0/0
    ip address 192.168.0.1 255.255.255.0
!
  interface Ethernet0/1
    ip address 192.168.1.1 255.255.255.0

    ip access-group printer_acl_in in
    ip access-group printer_acl_in out
!
!

SWITCH2.cfg
  hostname: SWITCH2
  version: 12.2
!
 ip access-list extended printer_acl_in
   permit ip 192.168.1.0 0.0.0.255 host 192.168.199.4
   permit ip 192.168.1.0 0.0.0.255 host 192.168.199.1
   permit ip 192.168.1.0 0.0.0.255 host 192.168.199.2
   exit
!
 ip access-list extended print_acl_out
   permit ip host 192.168.199.4 192.168.1.0 0.0.0.255
   permit ip host 192.168.199.1 192.168.1.0 0.0.0.255
   permit ip host 192.168.199.2 192.168.1.0 0.0.0.255
   exit
!
  interface Ethernet0/0
    ip address 192.168.0.2 255.255.255.0
!
  interface Ethernet0/1
    ip address 192.168.1.2 255.255.255.0

    ip access-group printer_acl_in in
    ip access-group printer_acl_in out

Obviously there can be more enhancements that could be made such as including tcp/udp ports or ranges, but I just wanted to show a basic example.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.