summaryrefslogtreecommitdiff
path: root/src/conf_mode/snmp.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/conf_mode/snmp.py')
-rwxr-xr-xsrc/conf_mode/snmp.py285
1 files changed, 167 insertions, 118 deletions
diff --git a/src/conf_mode/snmp.py b/src/conf_mode/snmp.py
index 3b47ffc98..06d2e253a 100755
--- a/src/conf_mode/snmp.py
+++ b/src/conf_mode/snmp.py
@@ -24,12 +24,12 @@ import pwd
import time
import jinja2
-import ipaddress
import random
import binascii
import re
import vyos.version
+import vyos.validate
from vyos.config import Config
from vyos import ConfigError
@@ -38,6 +38,7 @@ 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'
+config_file_init = r'/etc/default/snmpd'
# SNMP OIDs used to mark auth/priv type
OIDs = {
@@ -47,7 +48,7 @@ OIDs = {
'des' : '.1.3.6.1.6.3.10.1.2.2',
'none': '.1.3.6.1.6.3.10.1.2.1'
}
-# SNMPS template - be careful if you edit the template.
+# SNMP template (/etc/snmp/snmp.conf) - be careful if you edit the template.
client_config_tmpl = """
### Autogenerated by snmp.py ###
{% if trap_source -%}
@@ -56,39 +57,38 @@ clientaddr {{ trap_source }}
"""
-# SNMPS template - be careful if you edit the template.
+# SNMP template (/usr/share/snmp/snmpd.conf) - be careful if you edit the template.
access_config_tmpl = """
### Autogenerated by snmp.py ###
-{% if v3_users %}
-{% for u in v3_users %}
+{%- for u in v3_users %}
{{ u.mode }}user {{ u.name }}
-{% endfor %}
-{% endif -%}
+{%- endfor %}
+
rwuser {{ vyos_user }}
"""
-# SNMPS template - be careful if you edit the template.
+# SNMP template (/var/lib/snmp/snmpd.conf) - be careful if you edit the template.
user_config_tmpl = """
### Autogenerated by snmp.py ###
# user
-{% if v3_users %}
-{% for u in v3_users %}
-{% if u.authOID == 'none' %}
+{%- for u in v3_users %}
+{%- if u.authOID == 'none' %}
createUser {{ u.name }}
-{% elif u.authPassword %}
+{%- elif u.authPassword %}
createUser {{ u.name }} {{ u.authProtocol | upper }} "{{ u.authPassword }}" {{ u.privProtocol | upper }} {{ u.privPassword }}
-{% else %}
+{%- else %}
usmUser 1 3 {{ u.engineID }} "{{ u.name }}" "{{ u.name }}" NULL {{ u.authOID }} {{ u.authMasterKey }} {{ u.privOID }} {{ u.privMasterKey }} 0x
-{% endif %}
-{% endfor %}
-{% endif %}
+{%- endif %}
+{%- endfor %}
createUser {{ vyos_user }} MD5 "{{ vyos_user_pass }}" DES
+{%- if v3_engineid %}
oldEngineID {{ v3_engineid }}
+{%- endif %}
"""
-# SNMPS template - be careful if you edit the template.
+# SNMP template (/etc/snmp/snmpd.conf) - be careful if you edit the template.
daemon_config_tmpl = """
### Autogenerated by snmp.py ###
@@ -96,15 +96,10 @@ daemon_config_tmpl = """
sysObjectID 1.3.6.1.4.1.44641
sysServices 14
master agentx
-agentXPerms 0755 0755
+agentXPerms 0777 0777
pass .1.3.6.1.2.1.31.1.1.1.18 /opt/vyatta/sbin/if-mib-alias
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
-smuxpeer .1.3.6.1.4.1.3317.1.2.3
-smuxpeer .1.3.6.1.4.1.3317.1.2.5
-smuxpeer .1.3.6.1.4.1.3317.1.2.8
-smuxpeer .1.3.6.1.4.1.3317.1.2.9
smuxsocket localhost
# linkUp/Down configure the Event MIB tables to monitor
@@ -122,110 +117,112 @@ monitor -r 10 -e linkDownTrap "Generate linkDown" ifOperStatus == 2
########################
# configurable section #
########################
-
{% if v3_tsm_key %}
[snmp] localCert {{ v3_tsm_key }}
-{% endif %}
+{%- endif %}
# Default system description is VyOS version
sysDescr VyOS {{ version }}
-{% if description -%}
+{% if description %}
# Description
SysDescr {{ description }}
-{% endif %}
+{%- endif %}
# Listen
agentaddress unix:/run/snmpd.socket{% if listen_on %}{% for li in listen_on %},{{ li }}{% endfor %}{% else %},udp:161,udp6:161{% endif %}{% if v3_tsm_key %},tlstcp:{{ v3_tsm_port }},dtlsudp::{{ v3_tsm_port }}{% endif %}
# SNMP communities
-{% if communities -%}
-{% for c in communities %}
-{% if c.network -%}
-{% for network in c.network %}
+{%- for c in communities %}
+
+{%- if c.network_v4 %}
+{%- for network in c.network_v4 %}
{{ c.authorization }}community {{ c.name }} {{ network }}
-{{ c.authorization }}community6 {{ c.name }} {{ network }}
-{% endfor %}
-{% else %}
+{%- endfor %}
+{%- elif not c.has_source %}
{{ c.authorization }}community {{ c.name }}
+{%- endif %}
+
+{%- if c.network_v6 %}
+{%- for network in c.network_v6 %}
+{{ c.authorization }}community6 {{ c.name }} {{ network }}
+{%- endfor %}
+{%- elif not c.has_source %}
{{ c.authorization }}community6 {{ c.name }}
-{% endif %}
-{% endfor %}
-{% endif %}
+{%- endif %}
+
+{%- endfor %}
-{% if contact -%}
+{% if contact %}
# system contact information
SysContact {{ contact }}
-{% endif %}
+{%- endif %}
-{% if location -%}
+{% if location %}
# system location information
SysLocation {{ location }}
-{% endif %}
+{%- endif %}
{% if smux_peers -%}
# additional smux peers
-{% for sp in smux_peers %}
+{%- for sp in smux_peers %}
smuxpeer {{ sp }}
-{% endfor %}
-{% endif %}
+{%- endfor %}
+{%- endif %}
{% if trap_targets -%}
# if there is a problem - tell someone!
-{% for t in trap_targets %}
+{%- for t in trap_targets %}
trap2sink {{ t.target }}{% if t.port -%}:{{ t.port }}{% endif %} {{ t.community }}
-{% endfor %}
-{% endif %}
+{%- endfor %}
+{%- endif %}
+{%- if v3_enabled %}
#
# SNMPv3 stuff goes here
#
-{% if v3_enabled %}
-
# views
-{% if v3_views -%}
-{% for v in v3_views %}
-{% for oid in v.oids %}
+{%- for v in v3_views %}
+{%- for oid in v.oids %}
view {{ v.name }} included .{{ oid.oid }}
-{% endfor %}
-{% endfor %}
-{% endif %}
+{%- endfor %}
+{%- endfor %}
# access
# context sec.model sec.level match read write notif
-{% if v3_groups -%}
-{% for g in v3_groups %}
-{% if g.mode == 'ro' %}
-access {{ g.name }} "" usm {{ g.seclevel }} exact {{ g.view }} none none
-access {{ g.name }} "" tsm {{ g.seclevel }} exact {{ g.view }} none none
-{% elif g.mode == 'rw' %}
-access {{ g.name }} "" usm {{ g.seclevel }} exact {{ g.view }} {{ g.view }} none
-access {{ g.name }} "" tsm {{ g.seclevel }} exact {{ g.view }} {{ g.view }} none
-{% endif %}
-{% endfor -%}
-{% endif %}
+{%- for g in v3_groups %}
+access {{ g.name }} "" usm {{ g.seclevel }} exact {{ g.view }} {% if g.mode == 'ro' %}none{% else %}{{ g.view }}{% endif %} none
+access {{ g.name }} "" tsm {{ g.seclevel }} exact {{ g.view }} {% if g.mode == 'ro' %}none{% else %}{{ g.view }}{% endif %} none
+{%- endfor %}
# trap-target
-{% if v3_traps -%}
-{% for t in v3_traps %}
+{%- for t in v3_traps %}
trapsess -v 3 {{ '-Ci' if t.type == 'inform' }} -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 %}
+{%- endfor %}
# group
-{% if v3_users -%}
-{% for u in v3_users %}
+{%- for u in v3_users %}
group {{ u.group }} usm {{ u.name }}
group {{ u.group }} tsm {{ u.name }}
{% endfor %}
-{% endif %}
+{%- endif %}
+"""
-{% endif %}
+# SNMP template (/etc/default/snmpd) - be careful if you edit the template.
+init_config_tmpl = """
+### Autogenerated by snmp.py ###
+# This file controls the activity of snmpd
+
+# snmpd control (yes means start daemon).
+SNMPDRUN=yes
+# snmpd options (use syslog, close stdin/out/err).
+SNMPDOPTS='-LSed -u snmp -g snmp -p /run/snmpd.pid'
"""
default_config_data = {
'listen_on': [],
+ 'listen_address': [],
'communities': [],
'smux_peers': [],
'location' : '',
@@ -271,14 +268,32 @@ def get_config():
community = {
'name': name,
'authorization': 'ro',
- 'network': []
+ 'network_v4': [],
+ 'network_v6': [],
+ 'has_source' : False
}
if conf.exists('community {0} authorization'.format(name)):
community['authorization'] = conf.return_value('community {0} authorization'.format(name))
+ # Subnet of SNMP client(s) allowed to contact system
if conf.exists('community {0} network'.format(name)):
- community['network'] = conf.return_values('community {0} network'.format(name))
+ for addr in conf.return_values('community {0} network'.format(name)):
+ if vyos.validate.is_ipv4(addr):
+ community['network_v4'].append(addr)
+ else:
+ community['network_v6'].append(addr)
+
+ # IP address of SNMP client allowed to contact system
+ if conf.exists('community {0} client'.format(name)):
+ for addr in conf.return_values('community {0} client'.format(name)):
+ if vyos.validate.is_ipv4(addr):
+ community['network_v4'].append(addr)
+ else:
+ community['network_v6'].append(addr)
+
+ if (len(community['network_v4']) > 0) or (len(community['network_v6']) > 0):
+ community['has_source'] = True
snmp['communities'].append(community)
@@ -290,21 +305,20 @@ def get_config():
if conf.exists('listen-address'):
for addr in conf.list_nodes('listen-address'):
- listen = ''
port = '161'
if conf.exists('listen-address {0} port'.format(addr)):
port = conf.return_value('listen-address {0} port'.format(addr))
- if ipaddress.ip_address(addr).version == 4:
- # udp:127.0.0.1:161
- listen = 'udp:' + addr + ':' + port
- elif ipaddress.ip_address(addr).version == 6:
- # udp6:[::1]:161
- listen = 'udp6:' + '[' + addr + ']' + ':' + port
- else:
- raise ConfigError('Invalid IP address version')
+ snmp['listen_address'].append((addr, port))
- snmp['listen_on'].append(listen)
+ # Always listen on localhost if an explicit address has been configured
+ # This is a safety measure to not end up with invalid listen addresses
+ # that are not configured on this system. See https://phabricator.vyos.net/T850
+ if not '127.0.0.1' in conf.list_nodes('listen-address'):
+ snmp['listen_address'].append(('127.0.0.1', '161'))
+
+ if not '::1' in conf.list_nodes('listen-address'):
+ snmp['listen_address'].append(('::1', '161'))
if conf.exists('location'):
snmp['location'] = conf.return_value('location')
@@ -579,6 +593,24 @@ def verify(snmp):
if not os.path.isfile('/config/snmp/tls/certs/' + snmp['v3_tsm_key']):
raise ConfigError('TSM key must be fingerprint or filename in "/config/snmp/tls/certs/" folder')
+ for listen in snmp['listen_address']:
+ addr = listen[0]
+ port = listen[1]
+
+ if vyos.validate.is_ipv4(addr):
+ # example: udp:127.0.0.1:161
+ listen = 'udp:' + addr + ':' + port
+ else:
+ # example: udp6:[::1]:161
+ listen = 'udp6:' + '[' + addr + ']' + ':' + port
+
+ # We only wan't to configure addresses that exist on the system.
+ # Hint the user if they don't exist
+ if vyos.validate.is_addr_assigned(addr):
+ snmp['listen_on'].append(listen)
+ else:
+ print('WARNING: SNMP listen address {0} not configured!'.format(addr))
+
if 'v3_groups' in snmp.keys():
for group in snmp['v3_groups']:
#
@@ -641,48 +673,45 @@ def verify(snmp):
# Group must exist prior to mapping it into a group
# seclevel will be extracted from group
#
- error = True
if user['group']:
+ error = True
if 'v3_groups' in snmp.keys():
for group in snmp['v3_groups']:
if group['name'] == user['group']:
seclevel = group['seclevel']
error = False
- if error:
- raise ConfigError('You must create group "{0}" first'.format(user['group']))
+ if error:
+ raise ConfigError('You must create group "{0}" first'.format(user['group']))
# Depending on the configured security level
# the user has to provide additional info
- if seclevel in ('auth', 'priv'):
- if user['authPassword'] and user['authMasterKey']:
- raise ConfigError('Can not mix "encrypted-key" and "plaintext-key" for user auth')
+ if user['authPassword'] and user['authMasterKey']:
+ raise ConfigError('Can not mix "encrypted-key" and "plaintext-key" for user auth')
- if (not user['authPassword'] and not user['authMasterKey']):
- raise ConfigError('Must specify encrypted-key or plaintext-key for user auth')
+ if (not user['authPassword'] and not user['authMasterKey']):
+ raise ConfigError('Must specify encrypted-key or plaintext-key for user auth')
- # seclevel 'priv' is more restrictive
- if seclevel in ('priv'):
- if user['privPassword'] and user['privMasterKey']:
- raise ConfigError('Can not mix "encrypted-key" and "plaintext-key" for user privacy')
+ if user['privPassword'] and user['privMasterKey']:
+ raise ConfigError('Can not mix "encrypted-key" and "plaintext-key" for user privacy')
- if user['privPassword'] == '' and user['privMasterKey'] == '':
- raise ConfigError('Must specify encrypted-key or plaintext-key for user privacy')
+ if user['privPassword'] == '' and user['privMasterKey'] == '':
+ raise ConfigError('Must specify encrypted-key or plaintext-key for user privacy')
- if user['privMasterKey'] and user['engineID'] == '':
- raise ConfigError('Can not have "encrypted-key" without engineid')
+ if user['privMasterKey'] and user['engineID'] == '':
+ raise ConfigError('Can not have "encrypted-key" without engineid')
- if user['authPassword'] == '' and user['authMasterKey'] == '' and user['privTsmKey'] == '':
- raise ConfigError('Must specify auth or tsm-key for user auth')
+ if user['authPassword'] == '' and user['authMasterKey'] == '' and user['privTsmKey'] == '':
+ raise ConfigError('Must specify auth or tsm-key for user auth')
- if user['mode'] == '':
- raise ConfigError('Must specify user mode ro/rw')
+ if user['mode'] == '':
+ raise ConfigError('Must specify user mode ro/rw')
- if user['privTsmKey']:
- if not tsmKeyPattern.match(snmp['v3_tsm_key']):
- if not os.path.isfile('/etc/snmp/tls/certs/' + snmp['v3_tsm_key']):
- if not os.path.isfile('/config/snmp/tls/certs/' + snmp['v3_tsm_key']):
- raise ConfigError('User TSM key must be fingerprint or filename in "/config/snmp/tls/certs/" folder')
+ if user['privTsmKey']:
+ if not tsmKeyPattern.match(snmp['v3_tsm_key']):
+ if not os.path.isfile('/etc/snmp/tls/certs/' + snmp['v3_tsm_key']):
+ if not os.path.isfile('/config/snmp/tls/certs/' + snmp['v3_tsm_key']):
+ raise ConfigError('User TSM key must be fingerprint or filename in "/config/snmp/tls/certs/" folder')
if 'v3_views' in snmp.keys():
for view in snmp['v3_views']:
@@ -705,29 +734,35 @@ def generate(snmp):
return None
# Write client config file
- tmpl = jinja2.Template(client_config_tmpl, trim_blocks=True)
+ tmpl = jinja2.Template(client_config_tmpl)
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)
+ tmpl = jinja2.Template(daemon_config_tmpl)
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)
+ tmpl = jinja2.Template(access_config_tmpl)
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)
+ tmpl = jinja2.Template(user_config_tmpl)
config_text = tmpl.render(snmp)
with open(config_file_user, 'w') as f:
f.write(config_text)
+ # Write init config file
+ tmpl = jinja2.Template(init_config_tmpl)
+ config_text = tmpl.render(snmp)
+ with open(config_file_init, 'w') as f:
+ f.write(config_text)
+
return None
def apply(snmp):
@@ -761,9 +796,20 @@ def apply(snmp):
# start SNMP daemon
os.system("sudo systemctl restart snmpd.service")
- # the passwords are not available immediately so this is a workaround
- # and should be changed to polling
- time.sleep(2)
+ # Passwords are not available immediately in the configuration file,
+ # after daemon startup - we wait until they have been processed by
+ # snmpd, which we see when a magic line appears in this file.
+ snmpReady = False
+ while not snmpReady:
+ while not os.path.exists(config_file_user):
+ time.sleep(1)
+
+ with open(config_file_user, 'r') as f:
+ for line in f:
+ # Search for our magic string inside the file
+ if '**** DO NOT EDIT THIS FILE ****' in line:
+ snmpReady = True
+ break
# Back in the Perl days the configuration was re-read and any
# plaintext password inside the configuration was replaced by
@@ -795,6 +841,9 @@ def apply(snmp):
os.system('vyos_libexec_dir=/usr/libexec/vyos /opt/vyatta/sbin/my_delete service snmp v3 user "{0}" auth plaintext-key > /dev/null'.format(cfg['user']))
os.system('vyos_libexec_dir=/usr/libexec/vyos /opt/vyatta/sbin/my_delete service snmp v3 user "{0}" privacy plaintext-key > /dev/null'.format(cfg['user']))
+ # Enable AgentX in FRR
+ os.system('vtysh -c "configure terminal" -c "agentx" >/dev/null')
+
return None
if __name__ == '__main__':