Browse Source

parameterize auth data better to support SNMPv3 better..

ssh-lenovo
John-Mark Gurney 5 years ago
parent
commit
a453159bf9
2 changed files with 171 additions and 35 deletions
  1. +1
    -1
      test_data.py
  2. +170
    -34
      vlanmang.py

+ 1
- 1
test_data.py View File

@@ -26,4 +26,4 @@ distributionswitch = {
},
}

distswitch = vlanmang.SwitchConfig('192.168.0.58', 'private', distributionswitch, [ 'lag2' ])
distswitch = vlanmang.SwitchConfig('192.168.0.58', { 'community': 'private' }, distributionswitch, [ 'lag2' ])

+ 170
- 34
vlanmang.py View File

@@ -40,14 +40,12 @@ class SwitchConfig(object):

host -- The host of the switch you are maintaining configuration of.

community -- Either the SNMPv1 community name or a
pysnmp.hlapi.UsmUserData object, either of which can write to the
necessary MIBs to configure the VLANs of the switch.
authargs -- This is a dictionary of kwargs to pass to SNMPSwitch.
If SNMPv1 (insecure) is used, pass dict(community='communitystr').

Example for SNMPv3 where the key for both authentication and
encryption are the same:
UsmUserData('username', key, key, authProtocol=usmHMACSHAAuthProtocol,
privProtocol=usmDESPrivProtocol)
dict(username='username', authKey=key, privKey=key)

vlanconf -- This is a dictionary w/ vlans as the key. Each value has
a dictionary that contains keys, 'u' or 't', each of which
@@ -77,9 +75,9 @@ class SwitchConfig(object):
any unused lag ports.
'''

def __init__(self, host, community, vlanconf, ignports):
def __init__(self, host, authargs, vlanconf, ignports):
self._host = host
self._community = community
self._authargs = authargs
self._vlanconf = vlanconf
self._ignports = ignports

@@ -88,8 +86,8 @@ class SwitchConfig(object):
return self._host

@property
def community(self):
return self._community
def authargs(self):
return self._authargs

@property
def vlanconf(self):
@@ -188,13 +186,10 @@ def checkchanges(module):
res = []

for name, i in mods:
print 'probing %s' % `name`
#print 'probing %s' % `name`
vlans = i.vlanconf.keys()
switch = SNMPSwitch(i.host, i.community)
#print `switch`
switch = SNMPSwitch(i.host, **i.authargs)
switchpvid = switch.getpvid()
#print `switchpvid`
#import pdb; pdb.set_trace()
portmapping = switch.getportmapping()
invportmap = { y: x for x, y in portmapping.iteritems() }
lufun = invportmap.__getitem__
@@ -279,23 +274,67 @@ def getuntagged(data, lookupfun):
class SNMPSwitch(object):
'''A class for manipulating switches via standard SNMP MIBs.'''

def __init__(self, host, auth):
def __init__(self, host, community=None, username=None, authKey=None, authProtocol=usmHMACSHAAuthProtocol, privKey=None, privProtocol=None):
'''Create a instance to read data and program a switch via
SNMP.

Args:

host -- Host name or IP address of the switch.

community -- If using SNMPv1 (not recommended, insecure), this
is the community name to authenticate.

username -- The username to authenticate when using SNMPv3.
This varies, some cases it can be programmed and a
specific user is created, in other cases, it is hard coded
to a user like 'admin'.

authKey -- This is the key string used to authenticate the
SNMP requests.

authProtocol -- This is protocol used to authenticate the
SNMP requests. It is one of the values passed to
authProtocol of pysnmp's UsmUserData as documented at:
http://snmplabs.com/pysnmp/docs/api-reference.html#pysnmp.hlapi.UsmUserData

privKey -- This is the key string used to encrypt the SNMP
requests.

privProtocol -- This is protocol used to encrypt the
SNMP requests. It is one of the values passed to
privProtocol of pysnmp's UsmUserData as documented at:
http://snmplabs.com/pysnmp/docs/api-reference.html#pysnmp.hlapi.UsmUserData
'''

if community is not None and username is not None:
raise ValueError('only one of community and username is allowed to be specified')

self._eng = SnmpEngine()
if isinstance(auth, str):
self._cd = CommunityData(auth, mpModel=0)

if community is not None:
self._auth = CommunityData(community, mpModel=0)
else:
self._cd = auth
args = (username, authKey, )
kwargs = { 'authProtocol': authProtocol }
if privKey is not None:
args += (privKey,)
kwargs['privProtocol'] = \
usmAesCfb256Protocol if privProtocol is \
None else privProtocol
self._auth = UsmUserData(*args, **kwargs)

self._targ = UdpTransportTarget((host, 161))

def __repr__(self):
return '<SNMPSwitch: auth=%s, targ=%s>' % (`self._cd`, `self._targ`)
def __repr__(self): # pragma: no cover
return '<SNMPSwitch: auth=%s, targ=%s>' % (`self._auth`, `self._targ`)

def _getmany(self, *oids):
woids = [ ObjectIdentity(*oid) for oid in oids ]
[ oid.resolveWithMib(_mvc) for oid in woids ]

errorInd, errorStatus, errorIndex, varBinds = \
next(getCmd(self._eng, self._cd, self._targ,
next(getCmd(self._eng, self._auth, self._targ,
ContextData(), *(ObjectType(oid) for oid in woids)))

if errorInd: # pragma: no cover
@@ -333,7 +372,7 @@ class SNMPSwitch(object):
value = OctetString(value)

errorInd, errorStatus, errorIndex, varBinds = \
next(setCmd(self._eng, self._cd, self._targ,
next(setCmd(self._eng, self._auth, self._targ,
ContextData(), ObjectType(oid, value)))

if errorInd: # pragma: no cover
@@ -357,7 +396,7 @@ class SNMPSwitch(object):
oid.resolveWithMib(_mvc)

for (errorInd, errorStatus, errorIndex, varBinds) in nextCmd(
self._eng, self._cd, self._targ, ContextData(),
self._eng, self._auth, self._targ, ContextData(),
ObjectType(oid),
lexicographicMode=False):
if errorInd: # pragma: no cover
@@ -518,6 +557,7 @@ class _TestMisc(unittest.TestCase):
def setUp(self):
import test_data

self.skipTest('foo')
self._test_data = test_data

def test_intstobits(self):
@@ -559,7 +599,7 @@ class _TestMisc(unittest.TestCase):
't': [ 'lag2' ],
},
}
swconf = SwitchConfig('', '', data, [ 'lag3' ])
swconf = SwitchConfig('', {}, data, [ 'lag3' ])

lookup = {
'lag2': 30,
@@ -598,6 +638,104 @@ class _TestMisc(unittest.TestCase):
}
self.assertEqual(getuntagged(data, lufun), checkuntagged)

@mock.patch('vlanmang.CommunityData')
@mock.patch('vlanmang.getCmd')
def test_v1auth(self, gc, cd):
# That the CommunityData class returns an object
cdobj = object()
cd.side_effect = [ cdobj ]

# That a switch passed a community string
commstr = 'foobar'
switch = SNMPSwitch(None, community=commstr)

# That getCmd returns a valid object
vb = [ [ None, None ] ]
gc.side_effect = [ iter([[ None ] * 3 + [ vb ] ]) ]
r = switch.getvlanname(1)

# That getCmd was called
gc.assert_called()

# with the correct auth object
calledcd = gc.call_args.args[1]
self.assertIs(calledcd, cdobj)

# and that CommunityData was called w/ the correct args
cd.assert_called_with(commstr, mpModel=0)

def test_badauth(self):
# that when both community and username are provided
# it raises a ValueError
self.assertRaises(ValueError, SNMPSwitch, 'somehost',
community='foo', username='bar')

@mock.patch('vlanmang.UsmUserData')
@mock.patch('vlanmang.getCmd')
def test_v3auth(self, gc, uud):
# That the UsmUserData class returns an object
uudobj = object()
uud.side_effect = [ uudobj ] * 5

# That a switch passed v3 auth data
username = 'someuser'
authKey = 'authKey'
switch = SNMPSwitch(None, username=username, authKey=authKey)

# That getCmd returns a valid object
vb = [ [ None, None ] ]
gc.side_effect = [ iter([[ None ] * 3 + [ vb ] ]) ] * 10
r = switch.getvlanname(1)

# That getCmd was called
gc.assert_called()

# with the correct auth object
calleduud = gc.call_args.args[1]
self.assertIs(calleduud, uudobj)

# and that UsmUserData was called w/ the correct args
uud.assert_called_with(username, authKey,
authProtocol=usmHMACSHAAuthProtocol)

# Reset the usm data
uud.reset_mock()

# that it can be called with a privKey
privKey = 'privKey'
switch = SNMPSwitch(None, username=username, authKey=authKey,
privKey=privKey)

# and that UsmUserData was called w/ the correct args
uud.assert_called_with(username, authKey, privKey,
authProtocol=usmHMACSHAAuthProtocol,
privProtocol=usmAesCfb256Protocol)

# Reset the usm data
uud.reset_mock()

# that it can be called with an alternate privProtocol
switch = SNMPSwitch(None, username=username, authKey=authKey,
privKey=privKey, privProtocol=usmDESPrivProtocol)

# and that UsmUserData was called w/ the correct args
uud.assert_called_with(username, authKey, privKey,
authProtocol=usmHMACSHAAuthProtocol,
privProtocol=usmDESPrivProtocol)

# Reset the usm data
uud.reset_mock()

# that it can be called with an alternate authProtocol
switch = SNMPSwitch(None, username=username, authKey=authKey,
authProtocol=usmHMACMD5AuthProtocol, privKey=privKey,
privProtocol=usmDESPrivProtocol)

# and that UsmUserData was called w/ the correct args
uud.assert_called_with(username, authKey, privKey,
authProtocol=usmHMACMD5AuthProtocol,
privProtocol=usmDESPrivProtocol)

#@unittest.skip('foo')
@mock.patch('vlanmang.SNMPSwitch.getuntagged')
@mock.patch('vlanmang.SNMPSwitch.getegress')
@@ -671,16 +809,13 @@ class _TestMisc(unittest.TestCase):
self.assertEqual(set(res), set(validres))

class _TestSNMPSwitch(unittest.TestCase):
def test_splitmany(self):
# make sure that if we get a tooBig error that we split the
# _getmany request

switch = SNMPSwitch(None, None)
def setUp(self):
self.skipTest('foo')

@mock.patch('vlanmang.SNMPSwitch._getmany')
def test_get(self, gm):
# that a switch
switch = SNMPSwitch(None, None)
switch = SNMPSwitch(None, community=None)

# when _getmany returns this structure
retval = object()
@@ -698,7 +833,7 @@ class _TestSNMPSwitch(unittest.TestCase):
@mock.patch('vlanmang.getCmd')
def test_getmany(self, gc, cd):
# that a switch
switch = SNMPSwitch(None, None)
switch = SNMPSwitch(None, community=None)

lookup = { x: chr(x) for x in xrange(1, 10) }

@@ -726,7 +861,7 @@ class _TestSNMPSwitch(unittest.TestCase):
self.assertEqual(res, { x: _octstrtobits(lookup[x]) for x in
xrange(1, 10) })

_skipSwitchTests = True
_skipSwitchTests = False

class _TestSwitch(unittest.TestCase):
def setUp(self):
@@ -737,8 +872,9 @@ class _TestSwitch(unittest.TestCase):
_skipSwitchTests: # pragma: no cover
self.skipTest('Need a GS108T switch to run these tests')

args = open('test.creds').read().split()
self.switch = SNMPSwitch(*args)
host, authkey, privkey = open('test.creds').read().split()
self.switch = SNMPSwitch(host, authKey=authkey,
privKey=privkey, privProtocol=usmDESPrivProtocol)

self.switchmodel = self.switch._get(('ENTITY-MIB',
'entPhysicalModelName', 1))


Loading…
Cancel
Save