Skip to content

Commit

Permalink
Rework VACM access control function
Browse files Browse the repository at this point in the history
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
  • Loading branch information
etingof committed Jul 28, 2019
1 parent 0228371 commit ee9e897
Show file tree
Hide file tree
Showing 5 changed files with 508 additions and 119 deletions.
14 changes: 13 additions & 1 deletion CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@

Revision 4.4.10, released 2019-04-XX
Revision 4.4.10, released 2019-07-XX
------------------------------------

- Reworked VACM access control function. 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 `isAccessAllowed` lookup
performance

One potential incompatibility may be caused by the `addContext()` call
which now needs to be made explicitly during low-level VACM configuration
rather than be a side effect of `addVacmAccess()` call.

- Rebased MIB importing code onto `importlib` because `imp` is long
deprecated
- Fixed asyncore main loop to respect non-default timer resolution
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,14 @@ Advanced topics
:download:`Download</../../examples/v3arch/asyncore/agent/cmdrsp/multiple-snmp-engines.py>` script.


.. include:: /../../examples/v3arch/asyncore/agent/cmdrsp/detailed-vacm-configuration.py
:start-after: """
:end-before: """#

.. literalinclude:: /../../examples/v3arch/asyncore/agent/cmdrsp/detailed-vacm-configuration.py
:start-after: """#
:language: python

:download:`Download</../../examples/v3arch/asyncore/agent/cmdrsp/detailed-vacm-configuration.py>` script.

See also: :doc:`library reference </docs/api-reference>`.
117 changes: 117 additions & 0 deletions examples/v3arch/asyncore/agent/cmdrsp/detailed-vacm-configuration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
"""
Detailed VACM configuration
+++++++++++++++++++++++++++
Serves MIB subtrees under different conditions:
* Respond to SNMPv2c commands
* with SNMP community "public"
* over IPv4/UDP, listening at 127.0.0.1:161
* Serve MIB under non-default contextName `abcd`
* Allow access to `SNMPv2-MIB::system` subtree
* Although deny access to `SNMPv2-MIB::sysUpTime` by a bit mask
* Use partial context name matching (`a`)
This example demonstrates detailed VACM configuration performed via
low-level VACM calls: `addContext`, `addVacmGroup`, `addVacmAccess`
and `addVacmView`. Each function populates one of the tables
defined in `SNMP-VIEW-BASED-ACM-MIB` and used strictly as described
in the above mentioned MIB.
The following Net-SNMP's commands will GET a value at this Agent:
| $ snmpget -v2c -c public 127.0.0.1 SNMPv2-MIB::sysLocation.0
However this command will fail:
| $ snmpget -v2c -c public 127.0.0.1 SNMPv2-MIB::sysUpTime.0
This command will not reveal `SNMPv2-MIB::sysUpTime.0` among other objects:
| $ snmpwalk -v2c -c public 127.0.0.1 SNMPv2-MIB::system
"""#
from pysnmp.entity import engine, config
from pysnmp.entity.rfc3413 import cmdrsp, context
from pysnmp.carrier.asyncore.dgram import udp

# Create SNMP engine with autogenernated engineID and pre-bound
# to socket transport dispatcher
snmpEngine = engine.SnmpEngine()

# Transport setup

# UDP over IPv4
config.addTransport(
snmpEngine,
udp.domainName,
udp.UdpTransport().openServerMode(('127.0.0.1', 1161))
)

# Register default MIB instrumentation controller with a new SNMP context

contextName = 'abcd'

snmpContext = context.SnmpContext(snmpEngine)

snmpContext.registerContextName(
contextName, snmpEngine.msgAndPduDsp.mibInstrumController)

# Add new SNMP community name, map it to a new security name and
# SNMP context

securityName = 'my-area'
communityName = 'public'

config.addV1System(
snmpEngine, securityName, communityName,
contextEngineId=snmpContext.contextEngineId,
contextName=contextName)

# VACM configuration settings

securityModel = 2 # SNMPv2c
securityLevel = 1 # noAuthNoPriv

vacmGroup = 'my-group'
readViewName = 'my-read-view'

# We will match by context name prefix
contextPrefix = contextName[:1]

# Populate SNMP-VIEW-BASED-ACM-MIB::vacmContextTable
config.addContext(snmpEngine, contextName)

# Populate SNMP-VIEW-BASED-ACM-MIB::vacmSecurityToGroupTable
config.addVacmGroup(
snmpEngine, vacmGroup, securityModel, securityName)

# Populate SNMP-VIEW-BASED-ACM-MIB::vacmAccessTable
config.addVacmAccess(
snmpEngine, vacmGroup, contextPrefix, securityModel, securityLevel,
'prefix', readViewName, '', '')

# Populate SNMP-VIEW-BASED-ACM-MIB::vacmViewTreeFamilyTable

# Allow the whole system subtree
config.addVacmView(
snmpEngine, readViewName, 'included', '1.3.6.1.2.1.1.1', '1.1.1.1.1.1.1.0')

# ...but exclude one sub-branch (just one scalar OID)
config.addVacmView(
snmpEngine, readViewName, 'excluded', '1.3.6.1.2.1.1.3', '1.1.1.1.1.1.1.1')

# Register SNMP Applications at the SNMP engine for particular SNMP context
cmdrsp.GetCommandResponder(snmpEngine, snmpContext)
cmdrsp.SetCommandResponder(snmpEngine, snmpContext)
cmdrsp.NextCommandResponder(snmpEngine, snmpContext)

# Register an imaginary never-ending job to keep I/O dispatcher running forever
snmpEngine.transportDispatcher.jobStarted(1)

# Run I/O dispatcher which would receive queries and send responses
try:
snmpEngine.transportDispatcher.runDispatcher()

except Exception:
snmpEngine.transportDispatcher.closeDispatcher()
raise
58 changes: 35 additions & 23 deletions pysnmp/entity/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from pysnmp.proto.secmod.rfc3826.priv import aes
from pysnmp.proto.secmod.rfc7860.auth import hmacsha2
from pysnmp.proto.secmod.eso.priv import des3, aes192, aes256
from pysnmp.proto import rfc1902
from pysnmp.proto import rfc1905
from pysnmp import error

Expand Down Expand Up @@ -461,36 +462,31 @@ def __cookVacmAccessInfo(snmpEngine, groupName, contextName, securityModel,
return vacmAccessEntry, tblIdx


def addVacmAccess(snmpEngine, groupName, contextName, securityModel,
securityLevel, prefix, readView, writeView, notifyView):
vacmAccessEntry, tblIdx = __cookVacmAccessInfo(snmpEngine, groupName,
contextName, securityModel,
securityLevel)

addContext(snmpEngine, contextName)
def addVacmAccess(snmpEngine, groupName, contextPrefix, securityModel,
securityLevel, contextMatch, readView, writeView, notifyView):
vacmAccessEntry, tblIdx = __cookVacmAccessInfo(
snmpEngine, groupName, contextPrefix, securityModel,
securityLevel)

snmpEngine.msgAndPduDsp.mibInstrumController.writeVars(
((vacmAccessEntry.name + (9,) + tblIdx, 'destroy'),)
)
snmpEngine.msgAndPduDsp.mibInstrumController.writeVars(
((vacmAccessEntry.name + (1,) + tblIdx, contextName),
((vacmAccessEntry.name + (1,) + tblIdx, contextPrefix),
(vacmAccessEntry.name + (2,) + tblIdx, securityModel),
(vacmAccessEntry.name + (3,) + tblIdx, securityLevel),
(vacmAccessEntry.name + (4,) + tblIdx, prefix),
(vacmAccessEntry.name + (4,) + tblIdx, contextMatch),
(vacmAccessEntry.name + (5,) + tblIdx, readView),
(vacmAccessEntry.name + (6,) + tblIdx, writeView),
(vacmAccessEntry.name + (7,) + tblIdx, notifyView),
(vacmAccessEntry.name + (9,) + tblIdx, 'createAndGo'))
)


def delVacmAccess(snmpEngine, groupName, contextName, securityModel,
def delVacmAccess(snmpEngine, groupName, contextPrefix, securityModel,
securityLevel):
vacmAccessEntry, tblIdx = __cookVacmAccessInfo(snmpEngine, groupName,
contextName, securityModel,
securityLevel)

delContext(snmpEngine, contextName)
vacmAccessEntry, tblIdx = __cookVacmAccessInfo(
snmpEngine, groupName, contextPrefix, securityModel, securityLevel)

snmpEngine.msgAndPduDsp.mibInstrumController.writeVars(
((vacmAccessEntry.name + (9,) + tblIdx, 'destroy'),)
Expand All @@ -507,16 +503,30 @@ def __cookVacmViewInfo(snmpEngine, viewName, subTree):
return vacmViewTreeFamilyEntry, tblIdx


def addVacmView(snmpEngine, viewName, viewType, subTree, mask):
vacmViewTreeFamilyEntry, tblIdx = __cookVacmViewInfo(snmpEngine, viewName,
subTree)
def addVacmView(snmpEngine, viewName, viewType, subTree, subTreeMask):
vacmViewTreeFamilyEntry, tblIdx = __cookVacmViewInfo(
snmpEngine, viewName, subTree)

# Allow bitmask specification in form of an OID
if '.' in subTreeMask:
subTreeMask = rfc1902.ObjectIdentifier(subTreeMask)

if isinstance(subTreeMask, rfc1902.ObjectIdentifier):
subTreeMask = tuple(subTreeMask)
if len(subTreeMask) < len(subTree):
subTreeMask += (1,) * (len(subTree) - len(subTreeMask))

subTreeMask = rfc1902.OctetString.fromBinaryString(
''.join(str(x) for x in subTreeMask))

snmpEngine.msgAndPduDsp.mibInstrumController.writeVars(
((vacmViewTreeFamilyEntry.name + (6,) + tblIdx, 'destroy'),)
)

snmpEngine.msgAndPduDsp.mibInstrumController.writeVars(
((vacmViewTreeFamilyEntry.name + (1,) + tblIdx, viewName),
(vacmViewTreeFamilyEntry.name + (2,) + tblIdx, subTree),
(vacmViewTreeFamilyEntry.name + (3,) + tblIdx, mask),
(vacmViewTreeFamilyEntry.name + (3,) + tblIdx, subTreeMask),
(vacmViewTreeFamilyEntry.name + (4,) + tblIdx, viewType),
(vacmViewTreeFamilyEntry.name + (6,) + tblIdx, 'createAndGo'))
)
Expand Down Expand Up @@ -548,15 +558,16 @@ def addVacmUser(snmpEngine, securityModel, securityName, securityLevel,
(groupName, securityLevel, readView, writeView,
notifyView) = __cookVacmUserInfo(snmpEngine, securityModel, securityName,
securityLevel)
addContext(snmpEngine, contextName)
addVacmGroup(snmpEngine, groupName, securityModel, securityName)
addVacmAccess(snmpEngine, groupName, contextName, securityModel,
securityLevel, 1, readView, writeView, notifyView)
securityLevel, 'exact', readView, writeView, notifyView)
if readSubTree:
addVacmView(snmpEngine, readView, "included", readSubTree, null)
addVacmView(snmpEngine, readView, 'included', readSubTree, null)
if writeSubTree:
addVacmView(snmpEngine, writeView, "included", writeSubTree, null)
addVacmView(snmpEngine, writeView, 'included', writeSubTree, null)
if notifySubTree:
addVacmView(snmpEngine, notifyView, "included", notifySubTree, null)
addVacmView(snmpEngine, notifyView, 'included', notifySubTree, null)


def delVacmUser(snmpEngine, securityModel, securityName, securityLevel,
Expand All @@ -565,6 +576,7 @@ def delVacmUser(snmpEngine, securityModel, securityName, securityLevel,
(groupName, securityLevel, readView, writeView,
notifyView) = __cookVacmUserInfo(snmpEngine, securityModel,
securityName, securityLevel)
delContext(snmpEngine, contextName)
delVacmGroup(snmpEngine, securityModel, securityName)
delVacmAccess(snmpEngine, groupName, contextName, securityModel, securityLevel)
if readSubTree:
Expand Down
Loading

0 comments on commit ee9e897

Please sign in to comment.