Check Auth Open and Open Ports in Field Routers

Summary

Note

Credentials were modified to protect the privacy of the organization

Special attention should be paid to the type of Routers firmware that this script should configure - since most field routers were IOS, this script worked fine but some modification of delays and configuration prompts string differ in other device/ servers

The following scripts were created to address security needs in the field routers and the campus access switches. Similarly to to configurating no autostate to field routers, the shut open L2 ports addressed the need to secure open ports in field routers. In this environment, there are roughly +300 field sites that deploy C891 and C1111X routers. While these ports are ISE-secured, it was determined that we should shut down open ports (typically left open due to installation and removal of field endpoints).

Similarly, to address security needs a script was made to secure ISE auth open ports on campus access switches. There were no configuration phrase in this script due to potential impact to the users on campus. It was deemed less risky to find and monitor auth open switches and enforce the shutdown command after reviewing the auth open port manually.

Check Auth Open

from netmiko import ConnectHandler
import paramiko
import re

"""
Function: has_authentication_open(text)

This function checks if the given text contains the string "authentication open".

Args:
    text (str): String to search for the pattern.

Returns:
    bool: True if the pattern is found, False otherwise.
"""
def has_authentication_open(text):
    pattern = r"\bauthentication open\b"
    if re.search(pattern, text):
        return True
    else:
        return False

"""
This code block opens a file called "Test Sites.txt" containing a list of router hostnames or IP addresses.
It iterates over each line in the file and attempts to connect to the corresponding router.
"""
with open("Test Sites.txt", "r") as f:

    resultOutput = ""  # String to store the output

    for line in f:
        # Create a dictionary to store device connection parameters
        router = {
            'device_type': 'cisco_ios',
            'host': str(line),
            'username': 'cisco',
            'password': 'cisco123',
        }

        ip = str(line).rstrip("\n")  # Extract and strip newline from IP address

        try:
            # Connect to the router using Netmiko ConnectHandler
            router_connected = ConnectHandler(**router)
            prompt = router_connected.find_prompt()

            # Extract the hostname from the prompt
            hostname = prompt.split()[-1]

            # Check for "authentication open" in the running configuration
            check = router_connected.send_command("show running | include authentication ")

            if has_authentication_open(check):
                # Router has "authentication open" configured
                resultOutput = resultOutput + "Hostname: " + hostname + "(" + str(line).rstrip("\n") + ")" + " - \n"
                resultOutput = resultOutput + "This host has authentication open on at least one of its ports.\n"

                # Get list of all interfaces
                interfaces = router_connected.send_command("show ip interface brief")

                # Get detailed interface information (not used further in this script)
                output = router_connected.send_command("show ip interface brief")  

                # Iterate over the interfaces in the brief output
                for interface in interfaces.splitlines():

                    # The regex searches for strings in the variable interface that follow the format of either "GigabitEthernet" or "FastEthernet" followed by up to three digits separated by optional forward slashes
                    interfaceFound = re.findall(r"GigabitEthernet\d?\/?\d?\/?\d?|FastEthernet\d?\/?\d?\/?\d?", interface, re.IGNORECASE)

                    # Check for "authentication open" in the running configuration of the specific interface
                    checkInterface = router_connected.send_command("show run interface " + interfaceFound[0])

                    if has_authentication_open(checkInterface):
                        # Interface has "authentication open" configured
                        resultOutput = resultOutput + "Found Auth Open on %s\n" %(interfaceFound[0])
            else:
                # Router doesn't have "authentication open" configured
                # print("Checking " + hostname + "(" + str(line).rstrip("\n") + ")" + " (Skipped) ")
                continue

        except paramiko.ssh_exception.AuthenticationException as e:
            print('Error connecting to device ' + str(line).strip('\n') + ': {}'.format(e))

        finally:
            # Close the connection to the device.
            router_connected.disconnect()
            continue

# Print the final results
print(resultOutput)

Check Open and Shut L2 Ports in the Field Routers

"""
This script identifies down FastEthernet or GigabitEthernet interfaces on Cisco IOS devices 
listed in a file called SITES.txt and shuts them down.

1. Import libraries:
    - netmiko: Used for interacting with Cisco IOS devices.
    - paramiko: (Optional for netmiko) Provides underlying SSH functionality.
    - re: Used for regular expressions for pattern matching.
"""

from netmiko import ConnectHandler
import paramiko
import re
import networkUtil

'''
Function: isPhysicalPort(interface)

This function checks if a given interface string represents a physical FastEthernet or GigabitEthernet port.

Args:
    interface (str): String representing the interface name.

Returns:
    bool: True if the interface is FastEthernet or GigabitEthernet, False otherwise.
'''
def isPhysicalPort(interface):
    physical = False
    if re.search(r"FastEthernet", interface, re.IGNORECASE):
        physical = True
    if re.search(r"GigabitEthernet", interface, re.IGNORECASE):
        physical = True
    return physical

'''
Function: isDown(interface)

This function checks if the given interface status string indicates an "admin down" or "up" state.

Args:
    interface (str): String representing the interface output line.

Returns:
    bool: True if the interface state is not "admin down" or "up", False otherwise.
'''
def isDown(interface):
    # Create a regular expression object
    if re.search(r"admin|up", interface):
        return False
    else:
        return True

'''
This function checks if the given interface string contains an IP address format (xxx.xxx.xxx.xxx).

Args:
    line (str): String representing the interface output line.

Returns:
    bool: True if the line contains an IP address format, False otherwise.
'''
def contains_ip(line):
    ip_regex = r'\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b'
    match = re.search(ip_regex, line)
    return match is not None


'''
Opens the text "SITES.txt" file and iterates over each line. 
Each line should contains the IP address of a Cisco IOS device.
'''
with open("SITES.txt", "r") as f:

  # Iterate over the lines of the file.
  for line in f:

    router = {
    'device_type': 'cisco_ios',
    'host':   str(line),
    'username': 'cisco',
    'password': 'cisco123',
    }

    try:
        '''
        This block attempts to connect to the device using the netmiko library.
        It creates a connection handler object and uses it to interact with the device.
        '''
        command = []
        router_connected = ConnectHandler(**router)
        prompt = router_connected.find_prompt()

        # The device's hostname is the last word in the prompt.
        hostname = prompt.split()[-1]

        # Get a list of all the interfaces on the router with a down state using the "show ip interface brief | include down" command.
      
        interfaces = router_connected.send_command("show ip interface brief | include down")

        # Iterate over the interfaces and check if the state is down
        for interface in interfaces.splitlines():
            '''
            This section checks if the interface is down, a physical port, and doesn't have an IP address configured.
            If all conditions are met, it builds the shutdown command for that interface.
            '''
            if isDown(interface) and isPhysicalPort(interface) and not contains_ip(interface): 
                # Build the command to shutdown the port that is being checked
                interfaceFound = re.findall(r"GigabitEthernet\d?\/?\d?\/?\d?|FastEthernet\d?\/?\d?\/?\d?", interface, re.IGNORECASE)
                command.append("interface " + interfaceFound[0])
                command.append("shutdown ")
        
        router_connected.config_mode()            
        router_connected.send_config_set(command)
        router_connected.exit_config_mode()
        output = router_connected.send_command("show ip interface brief")
        router_connected.save_config()
        
    except paramiko.ssh_exception.AuthenticationException as e:
        print('Error connecting to device ' + str(line).strip('\n') + ': {}'.format(e))
        continue

    finally:
        # Close the connection to the device.
        router_connected.disconnect()
        continue