top of page
Search

Open Ports Housekeeping in SDA

  • Marco
  • Oct 14, 2022
  • 4 min read

An automated suggestion to get rid of forgotten manually opened ports.

Even in a fully automated SDA Fabric i find some security holes in the network, punched by manual interactions, on a regular basis.

A real world example happened to me recently. A customer of mine have fully implemented NAC (network access control) in a Cisco SDA environment. We spent weeks for designing and implementing. Some time later i came back for a meeting and i was sent to the meeting room. I plugged my Laptop in the network via a free port in the room. Boom, i was in the management network. How could that happened? We´ve fully implemented NAC.

The answer was a manual configuration change of a single port. A system engineer reserved the meeting room for a longer configuration session and set a port manually in a network in which he can reach the DNA Center. But forgot to reset the port configuration afterwards. There will always be a need for this scenario. But you should have a solution in an automated fashion in my opinion.


In this post i want to show a possible approach to use the beauty of DevNet to keep the ports clean.


The Idea is to search for opened Ports in the fabric and reset them to 802.1x enabled state. This script could run every night in a cron job for instance.


Lets dive into the docs and see which API Endpoints could help:



There´re two get-methods. One for APs and one for Users.

This is perfect to not reset ports assigned for APs (which by the way should be used for large scale). For our scenario we will use the user device request.




For the API call you need the Management IP and the Interface name.




At this moment you should realize, that you will run into the rate-limit of the DNAC, because you must request every single interface. I would suggest to use the SDK because you don´t have to handle the 429 rate-limiting.

As a response you get a dictionary:


{
    "status": "string",
    "description": "string",
    "siteNameHierarchy": "string",
    "deviceManagementIpAddress": "string",
    "interfaceName": "string",
    "dataIpAddressPoolName": "string",
    "voiceIpAddressPoolName": "string",
    "scalableGroupName": "string",
    "authenticateTemplateName": "string"
}

The interesting parts are status. If status is "success", the port is configured manually. If status is "failed" the port is in the default state of the fabric, so hopefully in closed authentication.


In case of "success", we can use the following API for resetting the port configuration, which will also need the management IP and the Interface name:




It is time for some code.


first create the DNA object


DNAC = DNACenterAPI(username=credentials.username, 
                    password=credentials.password, 
                    base_url=credentials.base_url, verify=False)

Because we want to iterate over all edge nodes, we now get a list of all devices with the role "ACCESS" and family of Switches and Hubs.


deviceList = DNAC.devices.get_device_list(role="ACCESS", family="Switches and Hubs").response

the response is a list with a dictionary per device with a bunch of attributes:


{
    "response": [
        {
            <output omitted>
            "upTime": "string",
            "deviceSupportLevel": "string",
            "hostname": "string",
            "type": "string",
            "softwareType": "string",
            "softwareVersion": "string",
            "description": "string",
            "roleSource": "string",
            "managementIpAddress": "string",
            "location": {},
            "role": "string",
            "instanceUuid": "string",
            "id": "string"
            <output omitted>
        }
    ]

After that we iterate through alle devices and their interfaces. It is important to catch any error. E.g. a device is currently unavailable or the requested device is not an edge note, even though we filtered based on role and family.


def get_interfaces(device, DNAC):
  try:
    return DNAC.devices.get_interface_info_by_id(device.id).response
  except:
    #returns None
    print(f"error reading interfaces frome device {device.hostname}")

Because the except statement returns None, it is necessary to catch this as well.


for device in deviceList:
  #empty the List
  interfaceList = []
  #if method returns None there was an error reading the device
  if get_interfaces(device, DNAC): 
    interfaceList = get_interfaces(device, DNAC)

The response of the get interface looks like this:



{
        "pid": "C9300-24UX",
        "vlanId": "0",
        "adminStatus": "UP",
        "mediaType": null,
        "ifIndex": "15",
        "mtu": "9100",
        "macAddress": "<..>",
        "addresses": null,
        "status": "down",
        "speed": "1000000",
        "deviceId": "b46b4268-e95c-4875-83d1-46ab620f68f0",
        "portName": "TenGigabitEthernet1/0/7",
        "managedNetworkElement": {
            "type": "ManagedNetworkElement",
            "id": 2890931,
            "url": "../../ManagedNetworkElement/2890931"
        },

Putting now together this information to reset the manually configured ports. But only the unplugged ports. Because we don´t want to change the configuration of ports on which somebody is currently working on.




def interfaceReset(interface, device):
    if DNAC.sda.get_port_assignment_for_user_device(
    device.managementIpAddress, interface.portName).status=="success" 
    and interface.status == "down":
        response = DNAC.sda.delete_port_assignment_for_user_device(
        device.managementIpAddress, interface.portName)
        

Unfortunately neither the SDK or the DNA Center will queue this call. If you send the API call to reset an interface while the DNAC is working on the previous one, some 500er errors will rise. Therefore a back-off mechanism is needed.

Using the task id delivered with the response we get following dictionary:


"response": {
        "progress": "TASK_MODIFY_PUT",
        "version": 1664968022944,
        "data": "workflow_id=0;cfs_id=0;rollback_status=not_supported;
        rollback_taskid=0;failure_task=NA;processcfs_complete=false",
        "startTime": 1664968022944,
        "username": "",
        "serviceType": "NCSP",
        "rootId": "b0891050-0859-46fc-9f5c-990a80c516b5",
        "isError": false,
        "instanceTenantId": "5f563655bb716700c9245fec",
        "id": "b0891050-0859-46fc-9f5c-990a80c516b5"
    },
    "version": "1.0"

Unfortunately the dict has no boolean element which tells us if the proccess is being finished. It is necessary to split the string value by the "data" key element.


task = DNAC.task.get_task_by_id(response.taskId)
        
task_status = "false"
while task_status == "false":
  task = DNAC.task.get_task_by_id(response.taskId)
  task_status = task.response.data.split(";")[5].split("=")[1]
  time.sleep(1)
print(f"interface {interface.portName} was reseted")


Putting all together:



#!/usr/bin/python


import credentials
from dnacentersdk import DNACenterAPI
import json
import time





#returns a list of all interfaces by a given device. 
def get_interfaces(device, DNAC):
    try:
      return DNAC.devices.get_interface_info_by_id(device.id).response
    except:
       #returns None
       print(f"error reading interfaces frome device {device.hostname}")
           
    
          

def interfaceReset(interface, device):

    if DNAC.sda.get_port_assignment_for_user_device(
    device.managementIpAddress, interface.portName).status=="success"
    and interface.status == "down":
        
        response = DNAC.sda.delete_port_assignment_for_user_device(
        device.managementIpAddress, interface.portName)
       
        task = DNAC.task.get_task_by_id(response.taskId)
        
        task_status = "false"
        while task_status == "false":
            task = DNAC.task.get_task_by_id(response.taskId)
            task_status = task.response.data.split(";")[5].split("=")[1]
            time.sleep(1)
        print(f"interface {interface.portName} was reseted")


 

if __name__ == '__main__':

    #login to DNAC
    DNAC = DNACenterAPI(username=credentials.username, 
                    password=credentials.password, 
                    base_url=credentials.base_url, verify=False)

    #get all Devices of the Role Access and family Switches and Hubs. Most likely Edge Nodes.
    deviceList = DNAC.devices.get_device_list(role="ACCESS", family="Switches and Hubs").response
    
    #iterate through all devices
    for device in deviceList:
        
        interfaceList = []
        #if method returns None there was an errer reading the device interfaces
        if get_interfaces(device, DNAC): 
          interfaceList = get_interfaces(device, DNAC)

          #itereate through all interfaces of that device and see if this interface shoulb be reseted
          for interface in interfaceList:
              interfaceReset(interface, device)



           

If you want to exclude some devices because you want to have open ports on purpose you can use something like a exclusion list and augment the the device iteration.


exclusionList= ["172.16.1.1", "10.1.255.1"]

for device in deviceList:
        if device.managementIpAddress not in exclusionList:


This is just an useful small skript. The intention behind is to give you some inspiration and new ideas. It hasn´t to be a huge komplex code all the time. Just start with small skripts. But keep starting. The outcome might be awesome! :)




 
 
 

Comments


Beitrag: Blog2 Post
  • LinkedIn

©2022 Marco Networking. Erstellt mit Wix.com

bottom of page