diff options
-rw-r--r-- | data/templates/snmp/etc.snmp.conf.tmpl | 2 | ||||
-rw-r--r-- | data/templates/snmp/etc.snmpd.conf.tmpl | 83 | ||||
-rw-r--r-- | data/templates/snmp/var.snmpd.conf.tmpl | 6 | ||||
-rw-r--r-- | interface-definitions/snmp.xml.in | 28 | ||||
-rw-r--r-- | python/vyos/snmpv3_hashgen.py | 51 | ||||
-rwxr-xr-x | src/conf_mode/snmp.py | 144 | ||||
-rwxr-xr-x | src/migration-scripts/snmp/1-to-2 | 89 |
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) |