Example Usage: A Contrived Configuration Audit
Suppose you have a large switched network and need to run audits on your configurations; assume you need to build configurations which conform to the following criteria:
Access switchports must be configured with
storm-control
Trunk ports must not have port-security
Timestamps must be enabled on logging and debug messages
You should follow the following steps.
Assume that you start with the following Cisco IOS configuration saved as short.conf
(All the interfaces need to be changed, to conform with audit requirements):
! Filename: short.conf
!
interface FastEthernet0/1
switchport mode access
switchport access vlan 532
!
interface FastEthernet0/2
switchport mode trunk
switchport trunk allowed 300,532
switchport nonegotiate
switchport port-security maximum 2
switchport port-security violation restrict
switchport port-security
!
interface FastEthernet0/3
switchport mode access
switchport access vlan 300
!
end
Next, we build this script to read and change the config:
from ciscoconfparse import CiscoConfParse
def standardize_intfs(parse):
## Search all switch interfaces and modify them
#
# r'^interface.+?thernet' is a regular expression, for ethernet intfs
for intf in parse.find_objects(r'^interface.+?thernet'):
has_stormcontrol = intf.has_child_with(r' storm-control broadcast')
is_switchport_access = intf.has_child_with(r'switchport mode access')
is_switchport_trunk = intf.has_child_with(r'switchport mode trunk')
## Add missing features
if is_switchport_access and (not has_stormcontrol):
intf.append_to_family(' storm-control action trap')
intf.append_to_family(' storm-control broadcast level 0.4 0.3')
## Remove dot1q trunk misconfiguration...
elif is_switchport_trunk:
intf.delete_children_matching('port-security')
## Parse the config
parse = CiscoConfParse('short.conf')
## Add a new switchport at the bottom of the config...
parse.append_line('interface FastEthernet0/4')
parse.append_line(' switchport')
parse.append_line(' switchport mode access')
parse.append_line('!')
parse.commit() # commit() **must** be called before searching again
## Search and standardize the interfaces...
standardize_intfs(parse)
parse.commit() # commit() **must** be called before searching again
## I'm illustrating regular expression usage in has_line_with()
if not parse.has_line_with(r'^service\stimestamp'):
## prepend_line() adds a line at the top of the configuration
parse.prepend_line('service timestamps debug datetime msec localtime show-timezone')
parse.prepend_line('service timestamps log datetime msec localtime show-timezone')
## Write the new configuration
parse.save_as('short.conf.new')
Normally, regular expressions should be used in .has_child_with()
;
however, you can technically get away with the bare strings that I used in
standardize_intfs()
in some cases. That said, regular expressions are
more powerful, and reliable when searching text. Usage of
the has_line_with()
and
find_objects()
methods illustrate regular
expression syntax.
After the script runs, the new configuration (short.conf.new
) looks like this:
service timestamps log datetime msec localtime show-timezone
service timestamps debug datetime msec localtime show-timezone
!
interface FastEthernet0/1
switchport mode access
switchport access vlan 532
storm-control broadcast level 0.4 0.3
storm-control action trap
!
interface FastEthernet0/2
switchport mode trunk
switchport trunk allowed 300,532
switchport nonegotiate
!
interface FastEthernet0/3
switchport mode access
switchport access vlan 300
storm-control broadcast level 0.4 0.3
storm-control action trap
!
interface FastEthernet0/4
switchport
switchport mode access
storm-control broadcast level 0.4 0.3
storm-control action trap
!
end
The script:
Added an access switchport:
interface FastEthernet0/4
Added
storm-control
to Fa0/1, Fa0/3, and Fa0/4Removed
port-security
from Fa0/2Added
timestamps
to logs and debug messages