Low Level Device functions for backup with Netbox and Gitlab

I’m planning a small blog post series about Cisco configuration backup with Netbox and Gitlab.

My idea is to “control” which devices are backed up by Netbox with Device Type, Site, IPv4 address and Status (“Active”).

Based on above information the script should then login with SSH to the devices, send a couple of commands like banner, wr mem, copy run scp to start a file transfer to save the configuration on a SCP host temporary directory.

The configuration files are then checked in into a GitLab repository so it’s easy to track and compare configuration files.

To outline the idea, I made the drawing shown below.

Concept Cisco configuration backup with Netbox and Gitlab
Concept Cisco configuration backup with Netbox and Gitlab

Low Level functions to connect to devices with pexpect

Function for Cisco Switch (IOS)

Because of different CLI syntax, the devices (or type of device) need their own function. For example the syntax for a Cisco switch (IOS) is different than the syntax of a Cisco Wireless Controller (AirEOS).

I’m connecting always with a “secure channel” (SSH) to the devices. pexpect is straight forward (expect/sendline) so I am not going into the detail.

What I like to do is to set an exec banner with the date and the information that a backup was done. After changing the banner it’s always a good idea to save the configuration in case the administrator forgot it ;)

After the wr mem command the configuration file is copied with SCP (Secure Copy) to the hosts tmp directory. In my case the script and the tmp directory is on the virtual machine where my Netbox installation is running.

If something goes wrong (for example script cannot log in or switch is down) the code is in a try/except block to handle errors.

  ###############################################################################
  def cliCiscoSwitch():
    try:
      print ("INFO: Connecting to device: " + hostname + " with IP: " + ipv4)
      sshconn = pexpect.spawn('ssh %s@%s' % (backup_user, ipv4))
      #sshconn.logfile = sys.stdout.buffer
      sshconn.timeout = 30

      sshconn.expect('.*assword:.*')
      sshconn.sendline(backup_pass)
      sshconn.expect('#')
      sshconn.sendline('term len 0')
      sshconn.expect('#')

      print ('INFO: Set exec banner')
      sshconn.sendline('conf t')
      sshconn.expect('#')
      sshconn.sendline('file prompt quiet')
      sshconn.expect('#')
      sshconn.sendline('banner exec ^')
      sshconn.sendline(today + ' - Config saved by backup2git')
      sshconn.sendline('^')
      sshconn.expect('#')
      sshconn.sendline('exit')

      print ('INFO: Executing write memory command')
      sshconn.sendline('wr mem')
      sshconn.expect('.*OK.*')

      print ('INFO: Executing copy run scp command')
      sshconn.sendline('copy run scp://' + scp_user + ':' + scp_pass + '@' + scp_host + '//tmp/' + hostname + '.cfg')
      sshconn.expect('.*copied.*')

      print ('INFO: Log out from device: ' + hostname + " with ip: " + ipv4)
      sshconn.sendline('logout')

    except pexpect.TIMEOUT:
      print ("ERROR: No login to device: " + hostname + " with ip: " + ipv4)
      pass
Python: Function for Cisco Switch (IOS)

Function for Cisco Wireless Controller

The same principle is valid for a Cisco Wireless Controller, however the CLI syntax is different. Instead of the IOS commands, AirEOS ueses save config, transfer upload. Please note that a file transfer with SCP is not possible, therefore I’m using SFTP (Secure FTP) to have a secure communication channel.

  ###############################################################################
  def cliCiscoWirelessController():
    try:
      print ("INFO: Connecting to device: " + hostname + " with IP: " + ipv4)
      sshconn = pexpect.spawn('ssh %s@%s' % (backup_user, ipv4))
      #sshconn.logfile = sys.stdout.buffer
      sshconn.timeout = 30

      sshconn.expect('.*ser:.*')
      sshconn.sendline(backup_user)
      sshconn.expect('.*assword:.*')
      sshconn.sendline(backup_pass)
      sshconn.expect('>')
      print ('INFO: Executing save config command')
      sshconn.sendline('save config')
      sshconn.expect('(y/n)')
      sshconn.sendline('y')

      print ('INFO: set transfer parameters')
      sshconn.sendline('transfer upload datatype config')
      sshconn.expect('>')
      sshconn.sendline('transfer upload mode sftp')
      sshconn.expect('>')
      sshconn.sendline('transfer upload serverip ' + scp_host)
      sshconn.expect('>')
      sshconn.sendline('transfer upload filename ' + hostname + ".cfg")
      sshconn.expect('>')
      sshconn.sendline('transfer upload path /tmp/')
      sshconn.expect('>')
      sshconn.sendline('transfer upload username ' + scp_user)
      sshconn.expect('>')
      sshconn.sendline('transfer upload password ' + scp_pass)
      sshconn.expect('>')

      print ('INFO: Executing transfer upload start command')
      sshconn.sendline('transfer upload start')
      sshconn.expect('(y/N)')
      sshconn.sendline('y')
      sshconn.expect('successfully.')

      print ('INFO: Log out from device: ' + hostname + " with ip: " + ipv4)
      sshconn.sendline('logout')

    except pexpect.TIMEOUT:
      print ("ERROR: No login to device: " + hostname + " with ip: " + ipv4)
      pass
Python: Function for Cisco Wireless Controller

Function for Cisco ASA Firewall

The Cisco ASA Firewall runs into a dialog when I use the scp command. To handle this dialog, I am just sending a newline in the assumption that the provided parameters are correct.

  ###############################################################################
  def cliCiscoAsaFirewall():
    try:
      print ("INFO: Connecting to device: " + hostname + " with IP: " + ipv4)
      sshconn = pexpect.spawn('ssh %s@%s' % (backup_user, ipv4))
      #sshconn.logfile = sys.stdout.buffer
      sshconn.timeout = 30

      sshconn.expect('.*assword:.*')
      sshconn.sendline(backup_pass)
      sshconn.expect('>')
      sshconn.sendline('enable')
      sshconn.expect('.*assword:.*')
      sshconn.sendline(backup_pass)

      print ('INFO: Executing write memory command')
      sshconn.sendline('wr mem')
      sshconn.expect('.*OK.*')

      print ('INFO: Executing copy run scp command')
      sshconn.sendline('copy run scp://' + scp_user + ':' + scp_pass + '@' + scp_host + '//tmp/' + hostname + '.cfg')
      sshconn.expect('.*ource')
      sshconn.sendline()
      sshconn.expect('.*ddress')
      sshconn.sendline()
      sshconn.expect('.*sername')
      sshconn.sendline()
      sshconn.expect('.*ilename')
      sshconn.sendline()
      sshconn.expect('continue connecting')
      sshconn.sendline('yes')
      sshconn.expect('copied')
      sshconn.sendline('logout')

      print ('INFO: Log out from device: ' + hostname + " with ip: " + ipv4)
      sshconn.sendline('logout')

    except pexpect.TIMEOUT:
      print ("ERROR: No login to device: " + hostname + " with ip: " + ipv4)
      pass
Python: Function for Cisco ASA Firewall

This is now the script output with the low level device functions.

As expected, the .cfg files are now waiting in the /tmp directory for the Gitlab repo checkin function…

Screenshot: Device functions and tmp directory
Screenshot: Device functions and tmp directory

Share: