Scott O'Brien

Ramblings and resources of my online life

User Accounting with Netflow

2014-07-08 05:54:52 +0000 UTC

At UOW we had a challenge.  We wanted to allow proxy-free internet, but wanted to keep an eye on how much data was being consumed by what sort of users.  For this we built Project Herbert http://uowits.github.io/herbert-gui/docs.html.

It uses netflow from inside our network and some syslog monitoring scripts to match up our private RFC1918 address space to the users who have it at that time, process the flows in near-realtime so we can adjust throttling and firewall policy to be reactive with the environment.

The idea was to build this as a distributed system and allow it to scale-out to deal with more load

Project Herbert

Logging NAT Translations on the Cisco ASA

2014-05-08 06:14:09 +0000 UTC

It’s often handy when dealing with infringement notices and the like to have NAT translations logged.  Sure a better way would be to record netflow from these devices (and include the translations) but for a quick syslog solution, you can always:

1
2
3
logging enable
logging list ToSyslog level critical
logging list ToSyslog message 305011

See http://www.cisco.com/c/en/us/td/docs/security/pix/pix63/system/message/63syslog/pixemsgs.html#wp1054604

Messages will look something like:

1
May 08 13:01:20 freewifi-asa.net.uow.edu.au %ASA-6-305011: Built dynamic TCP translation from inside:10.64.37.96/53008 to outside:192.131.251.2/49520

 

Finding overlaps in address space

2014-02-24 06:05:36 +0000 UTC

We have an interesting problem at my workplace, we have an MPLS VPN design for separation of security zones (e.g., staff from students.) and we don’t have MPLS support on our edge. With a L3 to the edge design though this means that while every edge switch has its own address space (per VRF), it also has a /30 uplink (once again, per VRF) back to the PE device.

While this (rightly or wrongly decided) slightly more complicated design in itself isn’t a problem, I’ve been working on programatically putting all of this data into an IPAM (IP Address Management) solution. Doing this from the devices themselves (as apposed to a spreadsheet where it was previously kept) has provided the best way moving forward, so good in fact, that my IPAM started throwing exceptions when duplicate IP addresses and overlapping spaces were attempted to be added in the system.

The following Python script uses my Cisco IOS python library to be able to identify IP address overlaps from a bunch of my saved device configs.

It works by the following:

  1. Adds all L3 interfaces addresses and secondary addresses to a list
  2. Sorts list such that larger subnets are at the front, smaller (/32’s and the such) are at the back of the list.
  3. Walks through each subnet, if the network addresses is not in the routing table, add it in, if it is, add it to a list of colliding subnets.
  4. With the list of colliding subnets (subnets that have been multiply defined, or are of overlapping size):
    1. If they are /30’s and there are only two places of definition, skip
    2. if the subnet values are not equal OR an address appears twice in the subnet.  Print it out as a colliding address (space)

You can find the script as a ghist here (note:  This assumes the devices have been picked and loaded as a Picked list of IOSDevice’s)

Automating VLAN changes for ESXi Switchports in Cisco IOS.

2014-01-29 10:51:35 +0000 UTC

6500

At the organisation I’m currently working for, we recently experienced what appears to be a common issue, VLAN’s trunked down to ESXi nodes were inconsistent.

In our DC, we’re still running the old school Cisco Catalyst switches.  If we were running a fabric, or Nexus switches we could put port profiles to action or if we lucky enough to have some equipment running Junos <3 we could be using apply-groups for this.

Being stuck with 6500’s in the DC as our L2 platform (and a VSS for the L3 MPLS PE) this is a quick little hack job to automate configuration changes across switchports in Cisco IOS based on dynamically finding ports that are matching an interface description.

In our DC, we have a bunch of switches (defined at the top of this script) and all our switchports that go to ESXi switchports that should have this template applied are matched on the interface description containing ‘ESXHOST’

This relies on the python EXScript module

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# This script is used for updating the VLANs that are trunked down to ESX boxes.
#  It works by logging in to each switch in the DC then looking at the interfaces that have 'ESXHOST'
#  in the description.  For each of those switchports, it will run the 'alter' commands

from Exscript.Account import Account
from Exscript.protocols import SSH2
import argparse
import getopt
import getpass
import sys

#A list of the switches to interigate.  These are the switches that have ESXi hosts in them
SWITCHES = [
    '10.0.0.2',
    '10.0.0.3',
    '10.0.0.4',
    '10.0.0.5',
    '10.0.0.7'
    ]

#Commands to execute for every switchport
vlan_commands = """
  switchport trunk allowed vlan add 5,10,15,20-25,30,40,50
  switchport trunk allowed vlan add 60,69,70,100-1000
"""

def query_yes_no(question, default="yes"):
    """Ask a yes/no question via raw_input() and return their answer.

    "question" is a string that is presented to the user.
    "default" is the presumed answer if the user just hits &lt;Enter&gt;.
        It must be "yes" (the default), "no" or None (meaning
        an answer is required of the user).

    The "answer" return value is one of "yes" or "no".
    """
    valid = {"yes":True,   "y":True,  "ye":True,
             "no":False,     "n":False}
    if default == None:
        prompt = " [y/n] "
    elif default == "yes":
        prompt = " [Y/n] "
    elif default == "no":
        prompt = " [y/N] "
    else:
        raise ValueError("invalid default answer: '%s'" % default)

    while True:
        sys.stdout.write(question + prompt)
        choice = raw_input().lower()
        if default is not None and choice == '':
            return valid[default]
        elif choice in valid:
            return valid[choice]
        else:
            sys.stdout.write("Please respond with 'yes' or 'no' "\
                             "(or 'y' or 'n').\n")

def returnInterfacesFromShowDescription(text):
    """
    Returns a LIST of interfaces from the output of 'show interface desc' from a cisco device
    """
    return [ x.split()[0] for x in text.split("\n") ]

def genUpdateConfigForInterfaces(config, interfaces):
    """
    Takes in a configuration block to run, a list of interfaces and generates the appropriate int range commands with the
    config block after
    """
    updateConfig = ""
    while len(interfaces) &gt; 0:
        updateConfig += "int range " + ",".join(interfaces[:5])
        interfaces = interfaces[5:]
        updateConfig += "\n  %s\n" % config.strip()
    return updateConfig

def updateSwitchportsWithDescription(switch, description, username, password, commands):
    """
    Function is used to log into a switch, find all switchports that CONTAINS the description
    with each of the said switchports, it will then execute the given commands.

    switch:  Device to log into
    description:  Search string to try and find in the switchport description
    username:  Username to log into the switch with
    password:  The password to log into the switch with
    commands:  A list of commands that should be executed
    """

    print "Working with switch: %s" % switch

    #establish a SSH connection to the host
    conn = SSH2()
    account = Account(username, password)
    conn.connect(switch)
    conn.login(account)

    #Do some pre-setup
    conn.execute('')

    #Make sure we're in super user mode
    conn.execute('enable')
    conn.execute('terminal length 0')

    #find all ports that have ESXHOST (or whatever is set in description) in the description
    conn.execute('show int desc | i %s' % description)
    ports = conn.response.strip()

    print "Ports are:"
    print ports
    portsList = returnInterfacesFromShowDescription(ports)[1:-1]

    #go into config mode and get ready to run the update commands
    #Generate a list of the update commands that should be ran
    updateCommands = genUpdateConfigForInterfaces(commands, portsList).split("\n")
    conn.execute('conf t')
    print conn.response.strip()
    print "! About to execute the following commands."
    for line in updateCommands:
        print "! " + line

    if query_yes_no('Execute Commands?', default="no"):
        #Run the set of commands given
        for command in updateCommands:
            conn.execute(command)
            print conn.response

        #Exit out of config mode
        conn.execute("exit")
        conn.execute("exit")
        print conn.response
        print

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description="DC ESX VLAN Utility")
    parser.add_argument('--user', help='The username that should be used to log into the switches with')

    args = parser.parse_args()
    if args.user:
        username = args.user
    else:
        username = getpass.getuser()

    password = getpass.getpass()

    # Generate a list of commands
    for switch in SWITCHES:
        updateSwitchportsWithDescription(switch, 'ESXHOST', username, password, vlan_commands)