diff options
author | Ilya Etingof <etingof@gmail.com> | 2019-07-29 09:57:45 +0200 |
---|---|---|
committer | Ilya Etingof <etingof@gmail.com> | 2019-07-29 21:37:34 +0200 |
commit | 6c7b09ac88be195db176c37ca7a197265ca978d0 (patch) | |
tree | 7424c9617f5895405843ebeda345a50688231f43 /pysnmp/proto/acmod | |
parent | 43cd9ab639a432c42c783f06772b4f3981b80c83 (diff) | |
download | pysnmp-git-6c7b09ac88be195db176c37ca7a197265ca978d0.tar.gz |
Rework VACM access control function (#287)
Most important changes include:
* Added subtree match negation support (vacmViewTreeFamilyType)
* Added subtree family mask support (vacmViewTreeFamilyMask)
* Added prefix content name matching support (vacmAccessContextMatch)
* Added key VACM tables caching for better lookup performance
Diffstat (limited to 'pysnmp/proto/acmod')
-rw-r--r-- | pysnmp/proto/acmod/rfc3415.py | 400 |
1 files changed, 319 insertions, 81 deletions
diff --git a/pysnmp/proto/acmod/rfc3415.py b/pysnmp/proto/acmod/rfc3415.py index 13d9de12..4d987666 100644 --- a/pysnmp/proto/acmod/rfc3415.py +++ b/pysnmp/proto/acmod/rfc3415.py @@ -17,127 +17,365 @@ class Vacm(object): _powOfTwoSeq = (128, 64, 32, 16, 8, 4, 2, 1) - def isAccessAllowed(self, snmpEngine, securityModel, securityName, - securityLevel, viewType, contextName, variableName): + def __init__(self): + self._contextBranchId = -1 + self._groupNameBranchId = -1 + self._accessBranchId = -1 + self._viewTreeBranchId = -1 + + self._contextMap = {} + self._groupNameMap = {} + self._accessMap = {} + self._viewTreeMap = {} + + def _addAccessEntry(self, groupName, contextPrefix, securityModel, + securityLevel, prefixMatch, readView, writeView, + notifyView): + if not groupName: + return + + groups = self._accessMap + + try: + views = groups[groupName] + + except KeyError: + views = groups[groupName] = {} + + for viewType, viewName in ( + ('read', readView), ('write', writeView), + ('notify', notifyView)): + + try: + matches = views[viewType] + + except KeyError: + matches = views[viewType] = {} + + try: + contexts = matches[prefixMatch] + + except KeyError: + contexts = matches[prefixMatch] = {} + + try: + models = contexts[contextPrefix] + + except KeyError: + models = contexts[contextPrefix] = {} + + try: + levels = models[securityModel] + + except KeyError: + levels = models[securityModel] = {} + + levels[securityLevel] = viewName + + def _getFamilyViewName(self, groupName, contextName, securityModel, securityLevel, viewType): + groups = self._accessMap + + try: + views = groups[groupName] + + except KeyError: + raise error.StatusInformation(errorIndication=errind.noGroupName) + + try: + matches = views[viewType] + + except KeyError: + raise error.StatusInformation(errorIndication=errind.noAccessEntry) + + try: + # vacmAccessTable #2: exact match shortcut + return matches[1][contextName][securityModel][securityLevel] + + except KeyError: + pass + + # vacmAccessTable #2: fuzzy look-up + + candidates = [] + + for match, names in matches.items(): + + for context, models in names.items(): + + if match == 1 and contextName != context: + continue + + if match == 2 and contextName[:len(context)] != context: + continue + + for model, levels in models.items(): + for level, viewName in levels.items(): + + # priorities: + # - matching securityModel + # - exact context name match + # - longer partial match + # - highest securityLevel + rating = securityModel == model, match == 1, len(context), level + + candidates.append((rating, viewName)) + + if not candidates: + raise error.StatusInformation(errorIndication=errind.notInView) + + candidates.sort() + + rating, viewName = candidates[0] + return viewName + + def isAccessAllowed(self, + snmpEngine, + securityModel, + securityName, + securityLevel, + viewType, + contextName, + variableName): mibInstrumController = snmpEngine.msgAndPduDsp.mibInstrumController mibBuilder = mibInstrumController.mibBuilder - debug.logger & debug.FLAG_ACL and debug.logger( + debug.logger & debug.flagACL and debug.logger( 'isAccessAllowed: securityModel %s, securityName %s, ' 'securityLevel %s, viewType %s, contextName %s for ' - 'variableName %s' % (securityModel, securityName, securityLevel, - viewType, contextName, variableName)) + 'variableName %s' % (securityModel, securityName, + securityLevel, viewType, contextName, + variableName)) - # 3.2.1 - vacmContextEntry, = mibBuilder.importSymbols( - 'SNMP-VIEW-BASED-ACM-MIB', 'vacmContextEntry') + # Rebuild contextName map if changed - tblIdx = vacmContextEntry.getInstIdFromIndices(contextName) + vacmContextName, = mibInstrumController.mibBuilder.importSymbols( + 'SNMP-VIEW-BASED-ACM-MIB', 'vacmContextName') - try: - vacmContextEntry.getNode( - vacmContextEntry.name + (1,) + tblIdx).syntax + if self._contextBranchId != vacmContextName.branchVersionId: + + self._contextMap.clear() + + nextMibNode = vacmContextName + + while True: + try: + nextMibNode = vacmContextName.getNextNode(nextMibNode.name) - except NoSuchInstanceError: + except NoSuchInstanceError: + break + + self._contextMap[nextMibNode.syntax] = True + + self._contextBranchId = vacmContextName.branchVersionId + + # 3.2.1 + if contextName not in self._contextMap: raise error.StatusInformation(errorIndication=errind.noSuchContext) - # 3.2.2 - vacmSecurityToGroupEntry, = mibBuilder.importSymbols( - 'SNMP-VIEW-BASED-ACM-MIB', 'vacmSecurityToGroupEntry') + # Rebuild groupName map if changed + + vacmGroupName, = mibInstrumController.mibBuilder.importSymbols( + 'SNMP-VIEW-BASED-ACM-MIB', 'vacmGroupName') + + if self._groupNameBranchId != vacmGroupName.branchVersionId: + + vacmSecurityToGroupEntry, = mibInstrumController.mibBuilder.importSymbols( + 'SNMP-VIEW-BASED-ACM-MIB', 'vacmSecurityToGroupEntry') + + self._groupNameMap.clear() + + nextMibNode = vacmGroupName + + while True: + try: + nextMibNode = vacmGroupName.getNextNode(nextMibNode.name) + + except NoSuchInstanceError: + break - tblIdx = vacmSecurityToGroupEntry.getInstIdFromIndices( - securityModel, securityName) + instId = nextMibNode.name[len(vacmGroupName.name):] + + indices = vacmSecurityToGroupEntry.getIndicesFromInstId(instId) + + self._groupNameMap[indices] = nextMibNode.syntax + + self._groupNameBranchId = vacmGroupName.branchVersionId + + # 3.2.2 + indices = securityModel, securityName try: - vacmGroupName = vacmSecurityToGroupEntry.getNode( - vacmSecurityToGroupEntry.name + (3,) + tblIdx).syntax + groupName = self._groupNameMap[indices] - except NoSuchInstanceError: + except KeyError: raise error.StatusInformation(errorIndication=errind.noGroupName) - # 3.2.3 - vacmAccessEntry, = mibBuilder.importSymbols( - 'SNMP-VIEW-BASED-ACM-MIB', 'vacmAccessEntry') + # Rebuild access map if changed - # XXX partial context name match - tblIdx = vacmAccessEntry.getInstIdFromIndices( - vacmGroupName, contextName, securityModel, securityLevel) + vacmAccessStatus, = mibInstrumController.mibBuilder.importSymbols( + 'SNMP-VIEW-BASED-ACM-MIB', 'vacmAccessStatus') - # 3.2.4 - if viewType == 'read': - entryIdx = vacmAccessEntry.name + (5,) + tblIdx + if self._accessBranchId != vacmAccessStatus.branchVersionId: - elif viewType == 'write': - entryIdx = vacmAccessEntry.name + (6,) + tblIdx + (vacmAccessEntry, + vacmAccessContextPrefix, + vacmAccessSecurityModel, + vacmAccessSecurityLevel, + vacmAccessContextMatch, + vacmAccessReadViewName, + vacmAccessWriteViewName, + vacmAccessNotifyViewName) = mibInstrumController.mibBuilder.importSymbols( + 'SNMP-VIEW-BASED-ACM-MIB', + 'vacmAccessEntry', + 'vacmAccessContextPrefix', + 'vacmAccessSecurityModel', + 'vacmAccessSecurityLevel', + 'vacmAccessContextMatch', + 'vacmAccessReadViewName', + 'vacmAccessWriteViewName', + 'vacmAccessNotifyViewName') - elif viewType == 'notify': - entryIdx = vacmAccessEntry.name + (7,) + tblIdx + self._accessMap.clear() - else: - raise error.ProtocolError('Unknown view type %s' % viewType) + nextMibNode = vacmAccessStatus - try: - viewName = vacmAccessEntry.getNode(entryIdx).syntax + while True: + try: + nextMibNode = vacmAccessStatus.getNextNode(nextMibNode.name) - except NoSuchInstanceError: - raise error.StatusInformation(errorIndication=errind.noAccessEntry) + except NoSuchInstanceError: + break - if not viewName: - raise error.StatusInformation(errorIndication=errind.noSuchView) + if nextMibNode.syntax != 1: # active row + continue - # XXX split onto object & instance ? + instId = nextMibNode.name[len(vacmAccessStatus.name):] - # 3.2.5a - vacmViewTreeFamilyEntry, = mibInstrumController.mibBuilder.importSymbols( - 'SNMP-VIEW-BASED-ACM-MIB', 'vacmViewTreeFamilyEntry') + indices = vacmAccessEntry.getIndicesFromInstId(instId) + + vacmGroupName = indices[0] + + self._addAccessEntry( + vacmGroupName, + vacmAccessContextPrefix.getNode( + vacmAccessContextPrefix.name + instId).syntax, + vacmAccessSecurityModel.getNode( + vacmAccessSecurityModel.name + instId).syntax, + vacmAccessSecurityLevel.getNode( + vacmAccessSecurityLevel.name + instId).syntax, + vacmAccessContextMatch.getNode( + vacmAccessContextMatch.name + instId).syntax, + vacmAccessReadViewName.getNode( + vacmAccessReadViewName.name + instId).syntax, + vacmAccessWriteViewName.getNode( + vacmAccessWriteViewName.name + instId).syntax, + vacmAccessNotifyViewName.getNode( + vacmAccessNotifyViewName.name + instId).syntax + ) + + self._accessBranchId = vacmAccessStatus.branchVersionId - tblIdx = vacmViewTreeFamilyEntry.getInstIdFromIndices(viewName) + viewName = self._getFamilyViewName( + groupName, contextName, securityModel, securityLevel, viewType) - # Walk over entries - initialTreeName = treeName = vacmViewTreeFamilyEntry.name + (2,) + tblIdx + # Rebuild family subtree map if changed - maskName = vacmViewTreeFamilyEntry.name + (3,) + tblIdx + vacmViewTreeFamilyViewName, = mibInstrumController.mibBuilder.importSymbols( + 'SNMP-VIEW-BASED-ACM-MIB', 'vacmViewTreeFamilyViewName') - while True: - vacmViewTreeFamilySubtree = vacmViewTreeFamilyEntry.getNextNode( - treeName) + if self._viewTreeBranchId != vacmViewTreeFamilyViewName.branchVersionId: - vacmViewTreeFamilyMask = vacmViewTreeFamilyEntry.getNextNode( - maskName) + (vacmViewTreeFamilySubtree, + vacmViewTreeFamilyMask, + vacmViewTreeFamilyType) = mibInstrumController.mibBuilder.importSymbols( + 'SNMP-VIEW-BASED-ACM-MIB', + 'vacmViewTreeFamilySubtree', + 'vacmViewTreeFamilyMask', + 'vacmViewTreeFamilyType') - treeName = vacmViewTreeFamilySubtree.name - maskName = vacmViewTreeFamilyMask.name + self._viewTreeMap.clear() + + powerOfTwo = [2 ** exp for exp in range(7, -1, -1)] + + nextMibNode = vacmViewTreeFamilyViewName + + while True: + try: + nextMibNode = vacmViewTreeFamilyViewName.getNextNode( + nextMibNode.name) + + except NoSuchInstanceError: + break + + if nextMibNode.syntax not in self._viewTreeMap: + self._viewTreeMap[nextMibNode.syntax] = [] + + instId = nextMibNode.name[len(vacmViewTreeFamilyViewName.name):] + + subtree = vacmViewTreeFamilySubtree.getNode( + vacmViewTreeFamilySubtree.name + instId).syntax + + mask = vacmViewTreeFamilyMask.getNode( + vacmViewTreeFamilyMask.name + instId).syntax + + mode = vacmViewTreeFamilyType.getNode( + vacmViewTreeFamilyType.name + instId).syntax + + mask = mask.asNumbers() + maskLength = min(len(mask) * 8, len(subtree)) + + ignoredSubOids = [ + i * 8 + j for i, octet in enumerate(mask) + for j, bit in enumerate(powerOfTwo) + if not (bit & octet) and i * 8 + j < maskLength + ] + + if ignoredSubOids: + pattern = list(subtree) + + for ignoredSubOid in ignoredSubOids: + pattern[ignoredSubOid] = 0 + + subtree = subtree.clone(pattern) + + entry = subtree, ignoredSubOids, mode == 1 + + self._viewTreeMap[nextMibNode.syntax].append(entry) + + for entries in self._viewTreeMap.values(): + entries.sort(key=lambda x: (len(x[0]), x[0])) + + self._viewTreeBranchId = vacmViewTreeFamilyViewName.branchVersionId + + # 3.2.5a + indices = viewName + + try: + entries = self._viewTreeMap[indices] - if initialTreeName != treeName[:len(initialTreeName)]: - # 3.2.5b - raise error.StatusInformation(errorIndication=errind.notInView) + except KeyError: + return error.StatusInformation(errorIndication=errind.notInView) - l = len(vacmViewTreeFamilySubtree.syntax) - if l > len(variableName): - continue + accessAllowed = False - if vacmViewTreeFamilyMask.syntax: - mask = [] - for c in vacmViewTreeFamilyMask.syntax.asNumbers(): - mask.extend([b & c for b in self._powOfTwoSeq]) + for entry in entries: + subtree, ignoredSubOids, included = entry - m = len(mask) - 1 - idx = l - 1 + if ignoredSubOids: + subOids = list(variableName) - while idx: - if (idx > m or mask[idx] and - vacmViewTreeFamilySubtree.syntax[idx] != variableName[idx]): - break + for ignoredSubOid in ignoredSubOids: + subOids[ignoredSubOid] = 0 - idx -= 1 + normalizedVariableName = subtree.clone(subOids) - if idx: - continue # no match + else: + normalizedVariableName = variableName - else: # no mask - if vacmViewTreeFamilySubtree.syntax != variableName[:l]: - continue # no match + if subtree.isPrefixOf(normalizedVariableName): + accessAllowed = included - # 3.2.5c - return error.StatusInformation(errorIndication=errind.accessAllowed) + # 3.2.5c + if not accessAllowed: + raise error.StatusInformation(errorIndication=errind.notInView) |