summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Poessinger <christian@poessinger.com>2020-07-11 17:55:21 +0200
committerChristian Poessinger <christian@poessinger.com>2020-07-11 17:55:21 +0200
commitd9c7dfb1e7a8ad4a44b571e3d4b8d87ff3898678 (patch)
treebd9a273b36de1357178cf6c2e13f482c981aac1d
parent8eb65fa66974e2b409fb367fe9fb2c5d65fc8332 (diff)
downloadvyos-1x-d9c7dfb1e7a8ad4a44b571e3d4b8d87ff3898678.tar.gz
vyos-1x-d9c7dfb1e7a8ad4a44b571e3d4b8d87ff3898678.zip
snmp: T2687: precalculate snmpv3 encrypted keys
As of now when adding new credentials for any SNMPv3 user we submit the credential either plaintext or encrypted. A plaintext credential will be hashed by SNMPd in the background and then passed back into the CLI so it's not stored in cleartext. This feels like the wrong way in changing the CLI content with data produced by a 3rd party daemon which implements the service. It feels like the tail wiggles the entire dog. This should be changed in the following way: - After retrieving the plaintext password from CLI, use Python to hash the key in advance - Re-populate the encrypted key into the CLI and drop the plaintext one - Generate service configuration and continue startup of SNMPd This also fixes a race condition when SNMPd started up but not properly provided the hasehd keys in the configuration resulting in a ConfigurationError. Now as we also support binding SNMPd to a VRF this fixes a deadlock situation on bootup as we can only bind late to the VRF and require up to 5 restarts of the service - but the service will never start.
-rw-r--r--data/templates/snmp/etc.snmp.conf.tmpl2
-rw-r--r--data/templates/snmp/etc.snmpd.conf.tmpl83
-rw-r--r--data/templates/snmp/var.snmpd.conf.tmpl6
-rw-r--r--interface-definitions/snmp.xml.in28
-rw-r--r--python/vyos/snmpv3_hashgen.py51
-rwxr-xr-xsrc/conf_mode/snmp.py144
-rwxr-xr-xsrc/migration-scripts/snmp/1-to-289
7 files changed, 257 insertions, 146 deletions
diff --git a/data/templates/snmp/etc.snmp.conf.tmpl b/data/templates/snmp/etc.snmp.conf.tmpl
index 159578906..6e4c6f063 100644
--- a/data/templates/snmp/etc.snmp.conf.tmpl
+++ b/data/templates/snmp/etc.snmp.conf.tmpl
@@ -1,4 +1,4 @@
### Autogenerated by snmp.py ###
-{% if trap_source -%}
+{% if trap_source %}
clientaddr {{ trap_source }}
{% endif %}
diff --git a/data/templates/snmp/etc.snmpd.conf.tmpl b/data/templates/snmp/etc.snmpd.conf.tmpl
index 1659abf93..278506350 100644
--- a/data/templates/snmp/etc.snmpd.conf.tmpl
+++ b/data/templates/snmp/etc.snmpd.conf.tmpl
@@ -32,87 +32,84 @@ sysDescr VyOS {{ version }}
{% 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{% if ipv6_enabled %},udp6:161{% endif %}{% endif %}
# SNMP communities
-{%- for c in communities %}
-
-{%- if c.network_v4 %}
-{%- for network in c.network_v4 %}
+{% for c in communities %}
+{% if c.network_v4 %}
+{% for network in c.network_v4 %}
{{ c.authorization }}community {{ c.name }} {{ network }}
-{%- endfor %}
-{%- elif not c.has_source %}
+{% endfor %}
+{% elif not c.has_source %}
{{ c.authorization }}community {{ c.name }}
-{%- endif %}
-
-{%- if c.network_v6 %}
-{%- for network in c.network_v6 %}
+{% endif %}
+{% if c.network_v6 %}
+{% for network in c.network_v6 %}
{{ c.authorization }}community6 {{ c.name }} {{ network }}
-{%- endfor %}
-{%- elif not c.has_source %}
+{% endfor %}
+{% elif not c.has_source %}
{{ c.authorization }}community6 {{ c.name }}
-{%- endif %}
-
-{%- endfor %}
+{% endif %}
+{% endfor %}
{% if contact %}
# system contact information
SysContact {{ contact }}
-{%- endif %}
+{% endif %}
{% if location %}
# system location information
SysLocation {{ location }}
-{%- endif %}
+{% endif %}
-{% if smux_peers -%}
+{% 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 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 %}
+{% for trap in trap_targets %}
+trap2sink {{ trap.target }}{{ ":" + trap.port if trap.port is defined }} {{ trap.community }}
+{% endfor %}
+{% endif %}
-{%- if v3_enabled %}
+{% if v3_enabled %}
#
# SNMPv3 stuff goes here
#
# views
-{%- for v in v3_views %}
-{%- for oid in v.oids %}
-view {{ v.name }} included .{{ oid.oid }}
-{%- endfor %}
-{%- endfor %}
+{% for view in v3_views %}
+{% for oid in view.oids %}
+view {{ view.name }} included .{{ oid.oid }}
+{% endfor %}
+{% endfor %}
# access
# context sec.model sec.level match read write notif
-{%- for g in v3_groups %}
-access {{ g.name }} "" usm {{ g.seclevel }} exact {{ g.view }} {% if g.mode == 'ro' %}none{% else %}{{ g.view }}{% endif %} none
-{%- endfor %}
+{% for group in v3_groups %}
+access {{ group.name }} "" usm {{ group.seclevel }} exact {{ group.view }} {% if group.mode == 'ro' %}none{% else %}{{ group.view }}{% endif %} none
+{% endfor %}
# trap-target
-{%- for t in v3_traps %}
+{% for t in v3_traps %}
trapsess -v 3 {{ '-Ci' if t.type == 'inform' }} -e {{ v3_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 %}
+{% endfor %}
# group
-{%- for u in v3_users %}
+{% for u in v3_users %}
group {{ u.group }} usm {{ u.name }}
-{% endfor %}
-{%- endif %}
+{% endfor %}
+{% endif %}
{% if script_ext %}
# extension scripts
-{%- for ext in script_ext|sort(attribute='name') %}
+{% for ext in script_ext|sort(attribute='name') %}
extend {{ ext.name }} {{ ext.script }}
-{%- endfor %}
+{% endfor %}
{% endif %}
diff --git a/data/templates/snmp/var.snmpd.conf.tmpl b/data/templates/snmp/var.snmpd.conf.tmpl
index 0b8e9f291..6cbc687ef 100644
--- a/data/templates/snmp/var.snmpd.conf.tmpl
+++ b/data/templates/snmp/var.snmpd.conf.tmpl
@@ -3,14 +3,12 @@
{%- for u in v3_users %}
{%- if u.authOID == 'none' %}
createUser {{ u.name }}
-{%- elif u.authPassword %}
-createUser {{ u.name }} {{ u.authProtocol | upper }} "{{ u.authPassword }}" {{ u.privProtocol | upper }} {{ u.privPassword }}
{%- else %}
-usmUser 1 3 {{ v3_engineid }} "{{ u.name }}" "{{ u.name }}" NULL {{ u.authOID }} {{ u.authMasterKey }} {{ u.privOID }} {{ u.privMasterKey }} 0x
+usmUser 1 3 0x{{ v3_engineid }} "{{ u.name }}" "{{ u.name }}" NULL {{ u.authOID }} 0x{{ u.authMasterKey }} {{ u.privOID }} 0x{{ u.privMasterKey }} 0x
{%- endif %}
{%- endfor %}
createUser {{ vyos_user }} MD5 "{{ vyos_user_pass }}" DES
{%- if v3_engineid %}
-oldEngineID {{ v3_engineid }}
+oldEngineID 0x{{ v3_engineid }}
{%- endif %}
diff --git a/interface-definitions/snmp.xml.in b/interface-definitions/snmp.xml.in
index 4894d0ce8..2fe8ce583 100644
--- a/interface-definitions/snmp.xml.in
+++ b/interface-definitions/snmp.xml.in
@@ -197,9 +197,9 @@
<children>
<leafNode name="engineid">
<properties>
- <help>Specifies the EngineID that uniquely identify an agent (e.g. 0xff42)</help>
+ <help>Specifies the EngineID that uniquely identify an agent (e.g. 000000000000000000000002)</help>
<constraint>
- <regex>(0x){0,1}([0-9a-f][0-9a-f]){1,18}$</regex>
+ <regex>^([0-9a-f][0-9a-f]){1,18}$</regex>
</constraint>
<constraintErrorMessage>ID must contain an even number (from 2 to 36) of hex digits</constraintErrorMessage>
</properties>
@@ -284,16 +284,16 @@
<help>Defines the privacy</help>
</properties>
<children>
- <leafNode name="encrypted-key">
+ <leafNode name="encrypted-password">
<properties>
<help>Defines the encrypted key for authentication</help>
<constraint>
- <regex>0x[0-9a-f]*$</regex>
+ <regex>^[0-9a-f]*$</regex>
</constraint>
<constraintErrorMessage>Encrypted key must only contain hex digits</constraintErrorMessage>
</properties>
</leafNode>
- <leafNode name="plaintext-key">
+ <leafNode name="plaintext-password">
<properties>
<help>Defines the clear text key for authentication</help>
<constraint>
@@ -341,16 +341,16 @@
<help>Defines the privacy</help>
</properties>
<children>
- <leafNode name="encrypted-key">
+ <leafNode name="encrypted-password">
<properties>
<help>Defines the encrypted key for privacy protocol</help>
<constraint>
- <regex>0x[0-9a-f]*$</regex>
+ <regex>^[0-9a-f]*$</regex>
</constraint>
<constraintErrorMessage>Encrypted key must only contain hex digits</constraintErrorMessage>
</properties>
</leafNode>
- <leafNode name="plaintext-key">
+ <leafNode name="plaintext-password">
<properties>
<help>Defines the clear text key for privacy protocol</help>
<constraint>
@@ -442,16 +442,16 @@
<help>Specifies the auth</help>
</properties>
<children>
- <leafNode name="encrypted-key">
+ <leafNode name="encrypted-password">
<properties>
<help>Defines the encrypted key for authentication</help>
<constraint>
- <regex>0x[0-9a-f]*$</regex>
+ <regex>^[0-9a-f]*$</regex>
</constraint>
<constraintErrorMessage>Encrypted key must only contain hex digits</constraintErrorMessage>
</properties>
</leafNode>
- <leafNode name="plaintext-key">
+ <leafNode name="plaintext-password">
<properties>
<help>Defines the clear text key for authentication</help>
<constraint>
@@ -514,16 +514,16 @@
<help>Defines the privacy</help>
</properties>
<children>
- <leafNode name="encrypted-key">
+ <leafNode name="encrypted-password">
<properties>
<help>Defines the encrypted key for privacy protocol</help>
<constraint>
- <regex>0x[0-9a-f]*$</regex>
+ <regex>^[0-9a-f]*$</regex>
</constraint>
<constraintErrorMessage>Encrypted key must only contain hex digits</constraintErrorMessage>
</properties>
</leafNode>
- <leafNode name="plaintext-key">
+ <leafNode name="plaintext-password">
<properties>
<help>Defines the clear text key for privacy protocol</help>
<constraint>
diff --git a/python/vyos/snmpv3_hashgen.py b/python/vyos/snmpv3_hashgen.py
new file mode 100644
index 000000000..38eca5314
--- /dev/null
+++ b/python/vyos/snmpv3_hashgen.py
@@ -0,0 +1,51 @@
+# Imported from https://github.com/TheMysteriousX/SNMPv3-Hash-Generator
+
+import hashlib
+import string
+import secrets
+
+from itertools import repeat
+
+P_LEN = 32
+E_LEN = 16
+
+class Hashgen(object):
+ @staticmethod
+ def md5(bytes):
+ return hashlib.md5(bytes).digest().hex()
+
+ @staticmethod
+ def sha1(bytes):
+ return hashlib.sha1(bytes).digest().hex()
+
+ @staticmethod
+ def expand(s, l):
+ reps = l // len(s) + 1 # approximation; worst case: overrun = l + len(s)
+ return ''.join(list(repeat(s, reps)))[:l]
+
+ @classmethod
+ def kdf(cls, password):
+ data = cls.expand(password, 1048576).encode('utf-8')
+ return hashlib.sha1(data).digest()
+
+ @staticmethod
+ def random_string(len=P_LEN, alphabet=(string.ascii_letters + string.digits)):
+ return ''.join(secrets.choice(alphabet) for _ in range(len))
+
+ @staticmethod
+ def random_engine(len=E_LEN):
+ return secrets.token_hex(len)
+
+ @classmethod
+ def derive_msg(cls, passphrase, engine):
+ # Parameter derivation รก la rfc3414
+ Ku = cls.kdf(passphrase)
+ E = bytearray.fromhex(engine)
+
+ return b''.join([Ku, E, Ku])
+
+ # Define available hash algorithms
+ algs = {
+ 'sha1': sha1,
+ 'md5': md5,
+ }
diff --git a/src/conf_mode/snmp.py b/src/conf_mode/snmp.py
index bafd26edc..f3c91d987 100755
--- a/src/conf_mode/snmp.py
+++ b/src/conf_mode/snmp.py
@@ -16,20 +16,16 @@
import os
-from binascii import hexlify
-from netifaces import interfaces
-from time import sleep
from sys import exit
from vyos.config import Config
from vyos.configverify import verify_vrf
+from vyos.snmpv3_hashgen import Hashgen
+from vyos.template import render
+from vyos.util import call
from vyos.validate import is_ipv4, is_addr_assigned
from vyos.version import get_version_data
-from vyos import ConfigError
-from vyos.util import call
-from vyos.template import render
-
-from vyos import airbag
+from vyos import ConfigError, airbag
airbag.enable()
config_file_client = r'/etc/snmp/snmp.conf'
@@ -61,7 +57,7 @@ default_config_data = {
'trap_targets': [],
'vyos_user': '',
'vyos_user_pass': '',
- 'version': '999',
+ 'version': '',
'v3_enabled': 'False',
'v3_engineid': '',
'v3_groups': [],
@@ -91,8 +87,8 @@ def get_config():
# create an internal snmpv3 user of the form 'vyosxxxxxxxxxxxxxxxx'
# os.urandom(8) returns 8 bytes of random data
- snmp['vyos_user'] = 'vyos' + hexlify(os.urandom(8)).decode('utf-8')
- snmp['vyos_user_pass'] = hexlify(os.urandom(16)).decode('utf-8')
+ snmp['vyos_user'] = 'vyos' + Hashgen.random_string(len=8)
+ snmp['vyos_user_pass'] = Hashgen.random_string(len=16)
if conf.exists('community'):
for name in conf.list_nodes('community'):
@@ -263,30 +259,30 @@ def get_config():
# 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)):
+ if conf.exists('v3 trap-target {0} auth plaintext-password'.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))
+ trap_cfg['authPassword'] = conf.return_value('v3 trap-target {0} auth plaintext-password'.format(trap))
- if conf.exists('v3 trap-target {0} auth encrypted-key'.format(trap)):
+ if conf.exists('v3 trap-target {0} auth encrypted-password'.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))
+ trap_cfg['authMasterKey'] = conf.return_value('v3 trap-target {0} auth encrypted-password'.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)):
+ if conf.exists('v3 trap-target {0} privacy plaintext-password'.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))
+ trap_cfg['privPassword'] = conf.return_value('v3 trap-target {0} privacy plaintext-password'.format(trap))
- if conf.exists('v3 trap-target {0} privacy encrypted-key'.format(trap)):
+ if conf.exists('v3 trap-target {0} privacy encrypted-password'.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))
+ trap_cfg['privMasterKey'] = conf.return_value('v3 trap-target {0} privacy encrypted-password'.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))
@@ -325,11 +321,11 @@ def get_config():
}
# 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 encrypted-password'.format(user)):
+ user_cfg['authMasterKey'] = conf.return_value('v3 user {0} auth encrypted-password'.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 plaintext-password'.format(user)):
+ user_cfg['authPassword'] = conf.return_value('v3 user {0} auth plaintext-password'.format(user))
# load default value
type = user_cfg['authProtocol']
@@ -349,11 +345,11 @@ def get_config():
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 encrypted-password'.format(user)):
+ user_cfg['privMasterKey'] = conf.return_value('v3 user {0} privacy encrypted-password'.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 plaintext-password'.format(user)):
+ user_cfg['privPassword'] = conf.return_value('v3 user {0} privacy plaintext-password'.format(user))
# load default value
type = user_cfg['privProtocol']
@@ -450,16 +446,16 @@ def verify(snmp):
if 'v3_traps' in snmp.keys():
for trap in snmp['v3_traps']:
if trap['authPassword'] and trap['authMasterKey']:
- raise ConfigError('Must specify only one of encrypted-key/plaintext-key for trap auth')
+ raise ConfigError('Must specify only one of encrypted-password/plaintext-key for trap auth')
if trap['authPassword'] == '' and trap['authMasterKey'] == '':
- raise ConfigError('Must specify encrypted-key or plaintext-key for trap auth')
+ raise ConfigError('Must specify encrypted-password or plaintext-key for trap auth')
if trap['privPassword'] and trap['privMasterKey']:
- raise ConfigError('Must specify only one of encrypted-key/plaintext-key for trap privacy')
+ raise ConfigError('Must specify only one of encrypted-password/plaintext-key for trap privacy')
if trap['privPassword'] == '' and trap['privMasterKey'] == '':
- raise ConfigError('Must specify encrypted-key or plaintext-key for trap privacy')
+ raise ConfigError('Must specify encrypted-password or plaintext-key for trap privacy')
if not 'type' in trap.keys():
raise ConfigError('v3 trap: "type" must be specified')
@@ -490,19 +486,12 @@ def verify(snmp):
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 user['authPassword'] and user['authMasterKey']:
- raise ConfigError('Can not mix "encrypted-key" and "plaintext-key" for user auth')
-
+ # Depending on the configured security level the user has to provide additional info
if (not user['authPassword'] and not user['authMasterKey']):
- raise ConfigError('Must specify encrypted-key or plaintext-key for user auth')
-
- if user['privPassword'] and user['privMasterKey']:
- raise ConfigError('Can not mix "encrypted-key" and "plaintext-key" for user privacy')
+ raise ConfigError('Must specify encrypted-password or plaintext-key for user auth')
if user['privPassword'] == '' and user['privMasterKey'] == '':
- raise ConfigError('Must specify encrypted-key or plaintext-key for user privacy')
+ raise ConfigError('Must specify encrypted-password or plaintext-key for user privacy')
if user['mode'] == '':
raise ConfigError('Must specify user mode ro/rw')
@@ -524,12 +513,35 @@ def generate(snmp):
for file in config_files:
rmfile(file)
- # Reload systemd manager configuration
- call('systemctl daemon-reload')
-
if not snmp:
return None
+ if 'v3_users' in snmp.keys():
+ # net-snmp is now regenerating the configuration file in the background
+ # thus we need to re-open and re-read the file as the content changed.
+ # After that we can no read the encrypted password from the config and
+ # replace the CLI plaintext password with its encrypted version.
+ os.environ["vyos_libexec_dir"] = "/usr/libexec/vyos"
+
+ for user in snmp['v3_users']:
+ hash = Hashgen.sha1 if user['authProtocol'] in 'sha1' else Hashgen.md5
+
+ if user['authPassword']:
+ Kul_auth = Hashgen.derive_msg(user['authPassword'], snmp['v3_engineid'])
+ user['authMasterKey'] = hash(Kul_auth)
+ user['authPassword'] = ''
+
+ call('/opt/vyatta/sbin/my_set service snmp v3 user "{name}" auth encrypted-password "{authMasterKey}" > /dev/null'.format(**user))
+ call('/opt/vyatta/sbin/my_delete service snmp v3 user "{name}" auth plaintext-password > /dev/null'.format(**user))
+
+ if user['privPassword']:
+ Kul_priv = Hashgen.derive_msg(user['privPassword'], snmp['v3_engineid'])
+ user['privMasterKey'] = hash(Kul_priv)
+ user['privPassword'] = ''
+
+ call('/opt/vyatta/sbin/my_set service snmp v3 user "{name}" privacy encrypted-password "{privMasterKey}" > /dev/null'.format(**user))
+ call('/opt/vyatta/sbin/my_delete service snmp v3 user "{name}" privacy plaintext-password > /dev/null'.format(**user))
+
# Write client config file
render(config_file_client, 'snmp/etc.snmp.conf.tmpl', snmp)
# Write server config file
@@ -544,50 +556,14 @@ def generate(snmp):
return None
def apply(snmp):
+ # Always reload systemd manager configuration
+ call('systemctl daemon-reload')
+
if not snmp:
return None
- # Reload systemd manager configuration
- call('systemctl daemon-reload')
# start SNMP daemon
- call("systemctl restart snmpd.service")
-
- if 'vrf' not in snmp.keys():
- # service will be restarted multiple times later on
- while (call('systemctl -q is-active snmpd.service') != 0):
- sleep(0.5)
-
- # net-snmp is now regenerating the configuration file in the background
- # thus we need to re-open and re-read the file as the content changed.
- # After that we can no read the encrypted password from the config and
- # replace the CLI plaintext password with its encrypted version.
- os.environ["vyos_libexec_dir"] = "/usr/libexec/vyos"
-
- # XXX: actually this whole logic makes less sense - why not calculate the
- # password hashed on our own and write them back into the config? I see
- # no valid reason in waiting for a third party process to do so.
- with open(config_file_user, 'r') as f:
- engineID = ''
- for line in f:
- if line.startswith('usmUser'):
- string = line.split(' ')
- cfg = {
- 'user': string[4].replace(r'"', ''),
- 'auth_pw': string[8],
- 'priv_pw': string[10]
- }
- # No need to take care about the VyOS internal user
- if cfg['user'] == snmp['vyos_user']:
- continue
-
- # Now update the running configuration
- #
- # Currently when executing call() the environment does not
- # have the vyos_libexec_dir variable set, see Phabricator T685.
- call('/opt/vyatta/sbin/my_set service snmp v3 user "{0}" auth encrypted-key "{1}" > /dev/null'.format(cfg['user'], cfg['auth_pw']))
- call('/opt/vyatta/sbin/my_set service snmp v3 user "{0}" privacy encrypted-key "{1}" > /dev/null'.format(cfg['user'], cfg['priv_pw']))
- call('/opt/vyatta/sbin/my_delete service snmp v3 user "{0}" auth plaintext-key > /dev/null'.format(cfg['user']))
- call('/opt/vyatta/sbin/my_delete service snmp v3 user "{0}" privacy plaintext-key > /dev/null'.format(cfg['user']))
+ call('systemctl restart snmpd.service')
# Enable AgentX in FRR
call('vtysh -c "configure terminal" -c "agentx" >/dev/null')
diff --git a/src/migration-scripts/snmp/1-to-2 b/src/migration-scripts/snmp/1-to-2
new file mode 100755
index 000000000..466a624e6
--- /dev/null
+++ b/src/migration-scripts/snmp/1-to-2
@@ -0,0 +1,89 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from sys import argv, exit
+from vyos.configtree import ConfigTree
+
+def migrate_keys(config, path):
+ # authentication: rename node 'encrypted-key' -> 'encrypted-password'
+ config_path_auth = path + ['auth', 'encrypted-key']
+ if config.exists(config_path_auth):
+ config.rename(config_path_auth, 'encrypted-password')
+ config_path_auth = path + ['auth', 'encrypted-password']
+
+ # remove leading '0x' from string if present
+ tmp = config.return_value(config_path_auth)
+ if tmp.startswith(prefix):
+ tmp = tmp.replace(prefix, '')
+ config.set(config_path_auth, value=tmp)
+
+ # privacy: rename node 'encrypted-key' -> 'encrypted-password'
+ config_path_priv = path + ['privacy', 'encrypted-key']
+ if config.exists(config_path_priv):
+ config.rename(config_path_priv, 'encrypted-password')
+ config_path_priv = path + ['privacy', 'encrypted-password']
+
+ # remove leading '0x' from string if present
+ tmp = config.return_value(config_path_priv)
+ if tmp.startswith(prefix):
+ tmp = tmp.replace(prefix, '')
+ config.set(config_path_priv, value=tmp)
+
+if __name__ == '__main__':
+ if (len(argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+ file_name = argv[1]
+
+ with open(file_name, 'r') as f:
+ config_file = f.read()
+
+ config = ConfigTree(config_file)
+ config_base = ['service', 'snmp', 'v3']
+
+ if not config.exists(config_base):
+ # Nothing to do
+ exit(0)
+ else:
+ # We no longer support hashed values prefixed with '0x' to unclutter
+ # CLI and also calculate the hases in advance instead of retrieving
+ # them after service startup - which was always a bad idea
+ prefix = '0x'
+
+ config_engineid = config_base + ['engineid']
+ if config.exists(config_engineid):
+ tmp = config.return_value(config_engineid)
+ if tmp.startswith(prefix):
+ tmp = tmp.replace(prefix, '')
+ config.set(config_engineid, value=tmp)
+
+ config_user = config_base + ['user']
+ if config.exists(config_user):
+ for user in config.list_nodes(config_user):
+ migrate_keys(config, config_user + [user])
+
+ config_trap = config_base + ['trap-target']
+ if config.exists(config_trap):
+ for trap in config.list_nodes(config_trap):
+ migrate_keys(config, config_trap + [trap])
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ exit(1)