summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--interface-definitions/snmp.xml55
-rwxr-xr-xsrc/conf_mode/snmp.py412
2 files changed, 440 insertions, 27 deletions
diff --git a/interface-definitions/snmp.xml b/interface-definitions/snmp.xml
index 698fb34a1..4bfb61576 100644
--- a/interface-definitions/snmp.xml
+++ b/interface-definitions/snmp.xml
@@ -20,7 +20,15 @@
<children>
<leafNode name="authorization">
<properties>
- <help>Authorization type (rw or ro) (default: 'ro')</help>
+ <help>Authorization type (default: 'ro')</help>
+ <valueHelp>
+ <format>ro</format>
+ <description>read only</description>
+ </valueHelp>
+ <valueHelp>
+ <format>rw</format>
+ <description>read write</description>
+ </valueHelp>
<constraint>
<regex>(ro|rw)</regex>
</constraint>
@@ -202,10 +210,17 @@
<leafNode name="seclevel">
<properties>
<help>Defines security level</help>
+ <valueHelp>
+ <format>auth</format>
+ <description>Requests must be authenticated</description>
+ </valueHelp>
+ <valueHelp>
+ <format>priv</format>
+ <description>Enforce use of encryption</description>
+ </valueHelp>
<constraint>
<regex>(auth|priv)</regex>
</constraint>
- <multi/>
</properties>
</leafNode>
<leafNode name="view">
@@ -538,6 +553,42 @@
</node>
</children>
</tagNode>
+ <tagNode name="view">
+ <properties>
+ <help>Specifies the view with name viewname</help>
+ <constraint>
+ <regex>^[^\(\)\|\-]+$</regex>
+ </constraint>
+ <constraintErrorMessage>Illegal characters in name</constraintErrorMessage>
+ </properties>
+ <children>
+ <tagNode name="oid">
+ <properties>
+ <help>Specifies the oid</help>
+ <constraint>
+ <regex>^[0-9]+(\\.[0-9]+)*$</regex>
+ </constraint>
+ <constraintErrorMessage>OID must start from a number</constraintErrorMessage>
+ </properties>
+ <children>
+ <leafNode name="exclude">
+ <properties>
+ <help>Exclude is an optional argument</help>
+ </properties>
+ </leafNode>
+ <leafNode name="mask">
+ <properties>
+ <help>Defines a bit-mask that is indicating which subidentifiers of the associated subtree OID should be regarded as significant</help>
+ <constraint>
+ <regex>^[0-9a-f]{2}([\\.:][0-9a-f]{2})*$</regex>
+ </constraint>
+ <constraintErrorMessage>MASK is a list of hex octets, separated by '.' or ':'</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </tagNode>
</children>
</node>
</children>
diff --git a/src/conf_mode/snmp.py b/src/conf_mode/snmp.py
index 227c00335..ef7ac12fc 100755
--- a/src/conf_mode/snmp.py
+++ b/src/conf_mode/snmp.py
@@ -20,14 +20,20 @@ import sys
import os
import jinja2
-import vyos.version
import ipaddress
+import random
+import binascii
+import os
+
+import vyos.version
from vyos.config import Config
from vyos import ConfigError
config_file_client = r'/etc/snmp/snmp.conf'
config_file_daemon = r'/etc/snmp/snmpd.conf'
+config_file_access = r'/usr/share/snmp/snmpd.conf'
+config_file_user = r'/var/lib/snmp/snmpd.conf'
# SNMPS template - be careful if you edit the template.
client_config_tmpl = """
@@ -39,38 +45,53 @@ clientaddr {{ trap_source }}
"""
# SNMPS template - be careful if you edit the template.
+access_config_tmpl = """
+### Autogenerated by snmp.py ###
+{% if v3_users %}
+{% for u in v3_users %}
+{{ u.mode }}user {{ u.name }}
+{% endfor %}
+{% endif -%}
+rwuser {{ vyos_user }}
+
+"""
+
+# SNMPS template - be careful if you edit the template.
+user_config_tmpl = """
+### Autogenerated by snmp.py ###
+# user
+{% if v3_users %}
+{% for u in v3_users %}
+createUser {{ u.name }} {{ u.authProtocol | upper }} {% if u.authPassword %} "{{ u.authPassword }}" {% elif u.authMasterKey %} "{{ u.authMasterKey }}"{% endif %} {{ u.privProtocol | upper }}{% if u.privPassword %} {{ u.privPassword }}{% elif u.privMasterKey %} {{ u.privMasterKey }}{% endif %}
+{% endfor %}
+{% endif %}
+
+createUser {{ vyos_user }} MD5 "{{ vyos_user_pass }}" DES
+"""
+
+# SNMPS template - be careful if you edit the template.
daemon_config_tmpl = """
### Autogenerated by snmp.py ###
+
# non configurable defaults
sysObjectID 1.3.6.1.4.1.44641
sysServices 14
-# maybe needed by lldpd
master agentx
agentXPerms 0755 0755
-# add hook to read IF-MIB::ifAlias from sysfs
pass .1.3.6.1.2.1.31.1.1.1.18 /opt/vyatta/sbin/if-mib-alias
-# ospfd
+smuxpeer .1.3.6.1.2.1.83
+smuxpeer .1.3.6.1.2.1.157
smuxpeer .1.3.6.1.4.1.3317.1.2.2
-# bgpd
-smuxpeer .1.3.6.1.4.1.3317.1.2.5
-# ripd
smuxpeer .1.3.6.1.4.1.3317.1.2.3
-# mribd
-smuxpeer .1.3.6.1.4.1.3317.1.2.9
-# mribd
-smuxpeer .1.3.6.1.2.1.83
-# pimd
+smuxpeer .1.3.6.1.4.1.3317.1.2.5
smuxpeer .1.3.6.1.4.1.3317.1.2.8
-# pimd
-smuxpeer .1.3.6.1.2.1.157
+smuxpeer .1.3.6.1.4.1.3317.1.2.9
smuxsocket localhost
# linkUp/Down configure the Event MIB tables to monitor
# the ifTable for network interfaces being taken up or down
# for making internal queries to retrieve any necessary information
-# create an internal snmpv3 user of the form 'vyattaxxxxxxxxxxxxxxxx'
-## TODO!!!!
-iquerySecName vyatta3392514e4189da84
+iquerySecName {{ vyos_user }}
# Modified from the default linkUpDownNotification
# to include more OIDs and poll more frequently
@@ -125,16 +146,75 @@ smuxpeer {{ sp }}
{% endfor %}
{% endif %}
+{% if trap_targets -%}
+# if there is a problem - tell someone!
+{% for t in trap_targets %}
+trap2sink {{ t.target }}{% if t.port -%}:{{ t.port }}{% endif %} {{ t.community }}
+{% endfor %}
+{% endif %}
+
+#
+# SNMPv3 stuff goes here
+#
+{% if v3_enabled %}
+
+# views
+{% if v3_views -%}
+{% for v in v3_views %}
+{% for oid in v.oids %}
+view {{ v.name }} included .{{ oid.oid }}
+{% endfor %}
+{% endfor %}
+{% endif %}
+
+# access
+# context sec.model sec.level match read write notif
+{% if v3_groups -%}
+{% for g in v3_groups %}
+access {{ g.name }} "" usm {{ g.seclevel }} exact {{ g.view }} none none
+access {{ g.name }} "" tsm {{ g.seclevel }} exact {{ g.view }} none none
+{% endfor -%}
+{% endif %}
+
+# trap-target
+{% if v3_traps -%}
+{% for t in v3_traps %}
+trapsess -v 3 -e {{ t.engineID }} -u {{ t.secName }} -l {{ t.secLevel }} -a {{ t.authProtocol }} {% if t.authPassword %}-A {{ t.authPassword }}{% elif t.authMasterKey %}-3m {{ t.authMasterKey }}{% endif %} -x {{ t.privProtocol }} {% if t.privPassword %}-X {{ t.privPassword }}{% elif t.privMasterKey %}-3M {{ t.privMasterKey }}{% endif %} {{ t.ipProto }}:{{ t.ipAddr }}:{{ t.ipPort }}
+{% endfor -%}
+{% endif %}
+
+# group
+{% if v3_users -%}
+{% for u in v3_users %}
+group {{ u.group }} usm {{ u.name }}
+group {{ u.group }} tsm {{ u.name }}
+{% endfor %}
+{% endif %}
+
+{% endif %}
+
"""
default_config_data = {
'listen_on': [],
+ 'communities': [],
+ 'smux_peers': [],
'location' : '',
'description' : '',
'contact' : '',
- 'communities': [],
'trap_source': '',
- 'smux_peers': []
+ 'trap_targets': [],
+ 'vyos_user': '',
+ 'vyos_user_pass': '',
+ 'version': '999',
+ 'v3_enabled': 'False',
+ 'v3_engineid': '',
+ 'v3_groups': [],
+ 'v3_traps': [],
+ 'v3_tsm_key': '',
+ 'v3_tsm_port': '10161',
+ 'v3_users': [],
+ 'v3_views': []
}
def get_config():
@@ -146,7 +226,12 @@ def get_config():
conf.set_level('service snmp')
version_data = vyos.version.get_version_data()
- snmp.setdefault('version', version_data['version'])
+ snmp['version'] = version_data['version']
+
+ # create an internal snmpv3 user of the form 'vyattaxxxxxxxxxxxxxxxx'
+ # os.urandom(8) returns 8 bytes of random data
+ snmp['vyos_user'] = 'vyatta' + binascii.hexlify(os.urandom(8)).decode('utf-8')
+ snmp['vyos_user_pass'] = binascii.hexlify(os.urandom(16)).decode('utf-8')
if conf.exists('community'):
for name in conf.list_nodes('community'):
@@ -197,35 +282,312 @@ def get_config():
if conf.exists('trap-source'):
snmp['trap_source'] = conf.return_value('trap-source')
+ if conf.exists('trap-target'):
+ for target in conf.list_nodes('trap-target'):
+ trap_tgt = {
+ 'target': target,
+ 'community': '',
+ 'port': ''
+ }
+
+ if conf.exists('trap-target {0} community'.format(target)):
+ trap_tgt['community'] = conf.return_value('trap-target {0} community'.format(target))
+
+ if conf.exists('trap-target {0} port'.format(target)):
+ trap_tgt['port'] = conf.return_value('trap-target {0} port'.format(target))
+
+ snmp['trap_targets'].append(trap_tgt)
+
+ #########################################################################
+ # ____ _ _ __ __ ____ _____ #
+ # / ___|| \ | | \/ | _ \ __ _|___ / #
+ # \___ \| \| | |\/| | |_) | \ \ / / |_ \ #
+ # ___) | |\ | | | | __/ \ V / ___) | #
+ # |____/|_| \_|_| |_|_| \_/ |____/ #
+ # #
+ # now take care about the fancy SNMP v3 stuff, or bail out eraly #
+ #########################################################################
+ if not conf.exists('v3'):
+ return snmp
+ else:
+ snmp['v3_enabled'] = True
+
+ #
+ # 'set service snmp v3 engineid'
+ #
+ if conf.exists('v3 engineid'):
+ snmp['v3_engineid'] = conf.return_value('v3 engineid')
+
+ #
+ # 'set service snmp v3 group'
+ #
+ if conf.exists('v3 group'):
+ for group in conf.list_nodes('v3 group'):
+ v3_group = {
+ 'name': group,
+ 'mode': 'ro',
+ 'seclevel': 'auth',
+ 'view': ''
+ }
+
+ if conf.exists('v3 group {0} mode'.format(group)):
+ v3_group['mode'] = conf.return_value('v3 group {0} mode'.format(group))
+
+ if conf.exists('v3 group {0} seclevel'.format(group)):
+ v3_group['seclevel'] = conf.return_value('v3 group {0} seclevel'.format(group))
+
+ if conf.exists('v3 group {0} view'.format(group)):
+ v3_group['view'] = conf.return_value('v3 group {0} view'.format(group))
+
+ snmp['v3_groups'].append(v3_group)
+
+ #
+ # 'set service snmp v3 trap-target'
+ #
+ if conf.exists('v3 trap-target'):
+ for trap in conf.list_nodes('v3 trap-target'):
+ trap_cfg = {
+ 'ipAddr': trap,
+ 'engineID': '',
+ 'secName': '',
+ 'authProtocol': 'md5',
+ 'authPassword': '',
+ 'authMasterKey': '',
+ 'privProtocol': 'des',
+ 'privPassword': '',
+ 'privMasterKey': '',
+ 'ipProto': 'udp',
+ 'ipPort': '162',
+ 'type': '',
+ 'secLevel': 'noAuthNoPriv'
+ }
+
+ if conf.exists('v3 trap-target {0} engineid'.format(trap)):
+ # Set the context engineID used for SNMPv3 REQUEST messages scopedPdu.
+ # If not specified, this will default to the authoritative engineID.
+ trap_cfg['engineID'] = conf.return_value('v3 trap-target {0} engineid'.format(trap))
+
+ if conf.exists('v3 trap-target {0} user'.format(trap)):
+ # Set the securityName used for authenticated SNMPv3 messages.
+ trap_cfg['secName'] = conf.return_value('v3 trap-target {0} user'.format(trap))
+
+ if conf.exists('v3 trap-target {0} auth type'.format(trap)):
+ # Set the authentication protocol (MD5 or SHA) used for authenticated SNMPv3 messages
+ # cmdline option '-a'
+ trap_cfg['authProtocol'] = conf.return_value('v3 trap-target {0} auth type'.format(trap))
+
+ if conf.exists('v3 trap-target {0} auth plaintext-key'.format(trap)):
+ # Set the authentication pass phrase used for authenticated SNMPv3 messages.
+ # cmdline option '-A'
+ trap_cfg['authPassword'] = conf.return_value('v3 trap-target {0} auth plaintext-key'.format(trap))
+
+ if conf.exists('v3 trap-target {0} auth encrypted-key'.format(trap)):
+ # Sets the keys to be used for SNMPv3 transactions. These options allow you to set the master authentication keys.
+ # cmdline option '-3m'
+ trap_cfg['authMasterKey'] = conf.return_value('v3 trap-target {0} auth encrypted-key'.format(trap))
+
+ if conf.exists('v3 trap-target {0} privacy type'.format(trap)):
+ # Set the privacy protocol (DES or AES) used for encrypted SNMPv3 messages.
+ # cmdline option '-x'
+ trap_cfg['privProtocol'] = conf.return_value('v3 trap-target {0} privacy type'.format(trap))
+
+ if conf.exists('v3 trap-target {0} privacy plaintext-key'.format(trap)):
+ # Set the privacy pass phrase used for encrypted SNMPv3 messages.
+ # cmdline option '-X'
+ trap_cfg['privPassword'] = conf.return_value('v3 trap-target {0} privacy plaintext-key'.format(trap))
+
+ if conf.exists('v3 trap-target {0} privacy encrypted-key'.format(trap)):
+ # Sets the keys to be used for SNMPv3 transactions. These options allow you to set the master encryption keys.
+ # cmdline option '-3M'
+ trap_cfg['privMasterKey'] = conf.return_value('v3 trap-target {0} privacy encrypted-key'.format(trap))
+
+ if conf.exists('v3 trap-target {0} protocol'.format(trap)):
+ trap_cfg['ipProto'] = conf.return_value('v3 trap-target {0} protocol'.format(trap))
+
+ if conf.exists('v3 trap-target {0} port'.format(trap)):
+ trap_cfg['ipPort'] = conf.return_value('v3 trap-target {0} port'.format(trap))
+
+ if conf.exists('v3 trap-target {0} type'.format(trap)):
+ tmp = conf.return_value('v3 trap-target {0} type'.format(trap))
+ # see http://www.net-snmp.org/docs/man/snmpd.conf.html
+ # The option -Ci can be used (with -v2c or -v3) to generate an INFORM
+ # notification rather than an unacknowledged TRAP.
+ if tmp == 'inform':
+ trap_cfg['type'] = ' -Ci'
+
+ # Determine securityLevel used for SNMPv3 messages (noAuthNoPriv|authNoPriv|authPriv).
+ # Appropriate pass phrase(s) must provided when using any level higher than noAuthNoPriv.
+ if trap_cfg['authPassword'] or trap_cfg['authMasterKey']:
+ if trap_cfg['privProtocol'] or trap_cfg['privPassword']:
+ trap_cfg['secLevel'] = 'authPriv'
+ else:
+ trap_cfg['secLevel'] = 'authNoPriv'
+
+ snmp['v3_traps'].append(trap_cfg)
+
+ #
+ # 'set service snmp v3 tsm'
+ #
+ if conf.exists('v3 tsm'):
+ if conf.exists('v3 tsm local-key'):
+ snmp['v3_tsm_key'] = conf.return_value('v3 tsm local-key')
+
+ if conf.exists('v3 tsm port'):
+ snmp['v3_tsm_port'] = conf.return_value('v3 tsm port')
+
+ #
+ # 'set service snmp v3 user'
+ #
+ if conf.exists('v3 user'):
+ for user in conf.list_nodes('v3 user'):
+ user_cfg = {
+ 'name': user,
+ 'authMasterKey': '',
+ 'authPassword': '',
+ 'authProtocol': 'md5',
+ 'engineID': '',
+ 'group': '',
+ 'mode': 'ro',
+ 'privMasterKey': '',
+ 'privPassword': '',
+ 'privTsmKey': '',
+ 'privProtocol': 'des'
+ }
+
+ #
+ # v3 user {0} auth
+ #
+ if conf.exists('v3 user {0} auth encrypted-key'.format(user)):
+ user_cfg['authMasterKey'] = conf.return_value('v3 user {0} auth encrypted-key'.format(user))
+
+ if conf.exists('v3 user {0} auth plaintext-key'.format(user)):
+ user_cfg['authPassword'] = conf.return_value('v3 user {0} auth plaintext-key'.format(user))
+
+ if conf.exists('v3 user {0} auth type'.format(user)):
+ user_cfg['authProtocol'] = conf.return_value('v3 user {0} auth type'.format(user))
+
+ #
+ # v3 user {0} engineid
+ #
+ if conf.exists('v3 user {0} engineid'.format(user)):
+ user_cfg['engineID'] = conf.return_value('v3 user {0} engineid'.format(user))
+
+ #
+ # v3 user {0} group
+ #
+ if conf.exists('v3 user {0} group'.format(user)):
+ user_cfg['group'] = conf.return_value('v3 user {0} group'.format(user))
+
+ #
+ # v3 user {0} mode
+ #
+ if conf.exists('v3 user {0} mode'.format(user)):
+ user_cfg['mode'] = conf.return_value('v3 user {0} mode'.format(user))
+
+ #
+ # v3 user {0} privacy
+ #
+ if conf.exists('v3 user {0} privacy encrypted-key'.format(user)):
+ user_cfg['privMasterKey'] = conf.return_value('v3 user {0} privacy encrypted-key'.format(user))
+
+ if conf.exists('v3 user {0} privacy plaintext-key'.format(user)):
+ user_cfg['privPassword'] = conf.return_value('v3 user {0} privacy plaintext-key'.format(user))
+
+ if conf.exists('v3 user {0} privacy tsm-key'.format(user)):
+ user_cfg['privTsmKey'] = conf.return_value('v3 user {0} privacy tsm-key'.format(user))
+
+ if conf.exists('v3 user {0} privacy type'.format(user)):
+ user_cfg['privProtocol'] = conf.return_value('v3 user {0} privacy type'.format(user))
+
+ snmp['v3_users'].append(user_cfg)
+
+ #
+ # 'set service snmp v3 view'
+ #
+ if conf.exists('v3 view'):
+ for view in conf.list_nodes('v3 view'):
+ view_cfg = {
+ 'name': view,
+ 'oids': []
+ }
+
+ if conf.exists('v3 view {0} oid'.format(view)):
+ for oid in conf.list_nodes('v3 view {0} oid'.format(view)):
+ oid_cfg = {
+ 'oid': oid
+ }
+ view_cfg['oids'].append(oid_cfg)
+ snmp['v3_views'].append(view_cfg)
+
return snmp
def verify(snmp):
+ if snmp is None:
+ return None
+
+ if 'v3_traps' in snmp.keys():
+ for trap in snmp['v3_traps']:
+ if trap['authPassword'] and trap['authMasterKey']:
+ raise ConfigError('Can not mix "encrypted-key" and "plaintext-key" for trap auth')
+ if trap['privPassword'] and trap['privMasterKey']:
+ raise ConfigError('Can not mix "encrypted-key" and "plaintext-key" for trap privacy')
+
+ if 'v3_users' in snmp.keys():
+ for user in snmp['v3_users']:
+ if user['authPassword'] and user['authMasterKey']:
+ raise ConfigError('Can not mix "encrypted-key" and "plaintext-key" for user auth')
+ if user['privPassword'] and user['privMasterKey']:
+ raise ConfigError('Can not mix "encrypted-key" and "plaintext-key" for user privacy')
+
+ if 'v3_group' in snmp.keys():
+ for group in snmp['v3_group']:
+ if not group['view']:
+ raise ConfigError('You must create a view first')
+
return None
def generate(snmp):
+ #
+ # As we are manipulating the snmpd user database we have to stop it first!
+ # This is even save if service is going to be removed
+ os.system("sudo systemctl stop snmpd.service")
+ os.unlink(config_file_client)
+ os.unlink(config_file_daemon)
+ os.unlink(config_file_access)
+ os.unlink(config_file_user)
+
if snmp is None:
return None
+ # Write client config file
tmpl = jinja2.Template(client_config_tmpl, trim_blocks=True)
config_text = tmpl.render(snmp)
with open(config_file_client, 'w') as f:
f.write(config_text)
+ # Write server config file
tmpl = jinja2.Template(daemon_config_tmpl, trim_blocks=True)
config_text = tmpl.render(snmp)
with open(config_file_daemon, 'w') as f:
f.write(config_text)
+ # Write access rights config file
+ tmpl = jinja2.Template(access_config_tmpl, trim_blocks=True)
+ config_text = tmpl.render(snmp)
+ with open(config_file_access, 'w') as f:
+ f.write(config_text)
+
+ # Write access rights config file
+ tmpl = jinja2.Template(user_config_tmpl, trim_blocks=True)
+ config_text = tmpl.render(snmp)
+ with open(config_file_user, 'w') as f:
+ f.write(config_text)
+
return None
def apply(snmp):
if snmp is not None:
os.system("sudo systemctl restart snmpd.service")
- else:
- # SNMP is removed in the commit
- os.system("sudo systemctl stop snmpd.service")
- os.unlink(config_file_client)
- os.unlink(config_file_daemon)
return None