Automating Port Security Configurations with Ansible
As a Network and Security engineer, I design, configure and manage over 30+ networks. This includes about 40 switches and over 30 gateways.
As you can imagine, managing this many devices manually, that is, logging into each device one by one to make repetitive changes can become cumbersome.
And not only is it cumbersome, it is inefficient.
Some examples of tasks that I automate are:
– Saving the show output of mac address tables
– Weekly configuration backups
– Firmware upgrades
Now that last task I mentioned above, firmware upgrades, use to take me about 117 minutes to complete. With network automation with ansible, I was able to cut that time down to about 10 minutes by automating firmware upgrades with playbooks. That’s a 91.5% reduction in time it takes to push firmware upgrades.
Automation and configuration management are essential skills that greatly enhance efficiency and reliability. Ansible is one of my go-to tools, and in this blog post, I want to walk you through a simple yet effective Ansible playbook for configuring port security on Cisco Layer 2 switches.
Scenario:
We are tasked with swapping out some old computers whose mac addresses has a 1:1 port-security mapping with switchports on switches at sites. These port-security configurations are configured via the following commands:
– switchport port-security
– switchport port-security mac-address {{ mac_address }}
– switchport port-security dynamic 0
– switchport port-security maximum 1
Now rather than log into each switch, and go into each port, we choose to automate the reconfigurations of mac addresses with an ansible playbook.
Let’s break down this Ansible playbook block by block:
Hosts and Connection Type
- hosts: layer2_switches
connection: network_cli
First things first: This targets all switches in the layer2_switches
group defined in my inventory. The network_cli
connection tells Ansible to use a network command-line interface, ideal for interacting with Cisco gear via SSH. There’s other modules we can use, like the cisco_ios
module, but this suffices for now.
Defining Variables
vars:
interface_configs:
- interface: fa 0/2
mac_address: xxxx.xxxx.xxxx
- interface: fa 0/3
mac_address: xxxx.xxxx.xxxx
- interface: fa 0/4
mac_address: xxxx.xxxx.xxxx
Here, I’m defining a simple, structured list of interfaces and the specific MAC addresses we want secured. This approach allows for us to define our values in a straightforward manner. Keep in mind, the loop
mechanism will iterate through this list in tasks 2 and 3.
Task 1: Checking Current Configurations
- name: Display current port-security configuration (before changes)
ios_command:
commands: show mac address-table static
register: show_output_before
- name: Display current MAC address configuration
debug:
msg: "Current MAC address configuration: {{ show_output_before.stdout_lines }}"
Before making changes, the playbook fetches and clearly displays the current port-security MAC configurations. Having a baseline helps track exactly what’s changed after running the playbook. Note that the text you put after name:
can be anything that makes sense to you, or the engineer deploying this playbook.
The show command are issued via ios_command
and commands:
Task 2: Setting Basic Port-Security
- name: Configure basic port-security settings
ios_config:
lines:
- switchport port-security maximum 1
- switchport port-security violation restrict
before:
- interface {{ item.interface }}
after:
- end
loop: "{{ interface_configs }}"
register: config_output
Next up, Ansible loops through each defined interface to enforce fundamental port-security settings:
Limits the number of allowed MAC addresses to two (
maximum 1
).Restricts unauthorized MAC addresses without disabling ports (
violation restrict
).
In this task, the ios_config:
issues the commands using lines
:.
before: is used to enter the interface configuration mode BEFORE issuing the commands via lines:
.
Task 3: Updating MAC Address Configurations
- name: Remove existing MAC addresses and configure new ones
ios_config:
lines:
- switchport port-security mac-address {{ item.mac_address }}
before:
- interface {{ item.interface }}
- no switchport port-security mac-address
after:
- end
loop: "{{ interface_configs }}"
register: mac_output
This task first clears any existing MAC addresses and then sets specific MAC addresses we’ve defined. These MAC addresses are the MAC addresses of the new computers we connect to the network.
Task 4: Saving Changes
- name: Save configuration
ios_command:
commands: write memory
register: save_output
We ensure all changes persist through device reboots by saving the configuration. Never skip this step! If you skip this step, and there is a power outage that causes the switches to power cycle, this can cause a network outage due to the switches reverting back to the previous, starting config without the new config we have configured.
Essentially, the old MAC addresses will be on the interfaces, preventing the new devices from gaining access to the network.
Task 5: Verifying the Update
- name: Show updated port-security configuration
ios_command:
commands: show port-security address
register: show_output_after
- name: Display updated port-security configuration
debug:
msg: "Updated MAC address configuration: {{ show_output_after.stdout_lines }}"
Lastly, we verify our updates by fetching the MAC table again, confirming our new configurations.
Also, it would be wise to use a version control mechanism such as github. We can add a task to the end of the playbook to push these show outputs up to github. That way, we have a version controlled repo of changes throughout time.
Final Thoughts
Automation like this not only saves time but enhances consistency and reduces errors across our network infrastructure.
As the number of devices we manage grows in our career, this becomes crucial. It is an essential method for managing network devices at scale.
Keep automating, and always keep learning!