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)  | 
