diff options
author | Christian Poessinger <christian@poessinger.com> | 2020-07-11 17:55:21 +0200 |
---|---|---|
committer | Christian Poessinger <christian@poessinger.com> | 2020-07-11 17:55:21 +0200 |
commit | d9c7dfb1e7a8ad4a44b571e3d4b8d87ff3898678 (patch) | |
tree | bd9a273b36de1357178cf6c2e13f482c981aac1d /src/conf_mode/snmp.py | |
parent | 8eb65fa66974e2b409fb367fe9fb2c5d65fc8332 (diff) | |
download | vyos-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.
Diffstat (limited to 'src/conf_mode/snmp.py')
-rwxr-xr-x | src/conf_mode/snmp.py | 144 |
1 files changed, 60 insertions, 84 deletions
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') |