From df231744b98202ec5fbcd236e795df7399747a0e Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Thu, 23 May 2019 07:38:54 -0500 Subject: T1397: Rewrite the config merge script Add vyos.config.show_config to show working configuration. Add vyos.remote.get_config_remote() for obtaining remote config files. --- python/vyos/config.py | 15 ++++++ python/vyos/remote.py | 133 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 148 insertions(+) create mode 100644 python/vyos/remote.py (limited to 'python') diff --git a/python/vyos/config.py b/python/vyos/config.py index bcf04225b..9a5125eb9 100644 --- a/python/vyos/config.py +++ b/python/vyos/config.py @@ -169,6 +169,21 @@ class Config(object): except VyOSError: return False + def show_config(self, path='', default=None): + """ + Args: + path (str): Configuration tree path, or empty + default (str): Default value to return + + Returns: + str: working configuration + """ + try: + out = self._run(self._make_command('showConfig', path)) + return out + except VyOSError: + return(default) + def is_multi(self, path): """ Args: diff --git a/python/vyos/remote.py b/python/vyos/remote.py new file mode 100644 index 000000000..372780c91 --- /dev/null +++ b/python/vyos/remote.py @@ -0,0 +1,133 @@ +# Copyright 2019 VyOS maintainers and contributors +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see . + +import sys +import os +import re +import fileinput +import subprocess + + +def check_and_add_host_key(host_name): + """ + Filter host keys and prompt for adding key to known_hosts file, if + needed. + """ + known_hosts = '{}/.ssh/known_hosts'.format(os.getenv('HOME')) + + keyscan_cmd = 'ssh-keyscan -t rsa {} 2>/dev/null'.format(host_name) + + try: + host_key = subprocess.check_output(keyscan_cmd, shell=True, + stderr=subprocess.DEVNULL, + universal_newlines=True) + except subprocess.CalledProcessError as err: + sys.exit("Can not get RSA host key") + + # libssh2 (jessie; stretch) does not recognize ec host keys, and curl + # will fail with error 51 if present in known_hosts file; limit to rsa. + usable_keys = False + offending_keys = [] + for line in fileinput.input(known_hosts, inplace=True): + if host_name in line and 'ssh-rsa' in line: + if line.split()[-1] != host_key.split()[-1]: + offending_keys.append(line) + continue + else: + usable_keys = True + if host_name in line and not 'ssh-rsa' in line: + continue + + sys.stdout.write(line) + + if usable_keys: + return + + if offending_keys: + print("Host key has changed!") + print("If you trust the host key fingerprint below, continue.") + + fingerprint_cmd = 'ssh-keygen -lf /dev/stdin <<< "{}"'.format(host_key) + try: + fingerprint = subprocess.check_output(fingerprint_cmd, shell=True, + stderr=subprocess.DEVNULL, + universal_newlines=True) + except subprocess.CalledProcessError as err: + sys.exit("Can not get RSA host key fingerprint.") + + print("RSA host key fingerprint is {}".format(fingerprint.split()[1])) + response = input("Do you trust this host? [y]/n ") + + if not response or response == 'y': + with open(known_hosts, 'a+') as f: + print("Adding {} to the list of known" + " hosts.".format(host_name)) + f.write(host_key) + else: + sys.exit("Host not trusted") + +def get_remote_config(remote_file): + """ Invoke curl to download remote (config) file. + + Args: + remote file URI: + scp://[:]@/ + sftp://[:]@/ + http:/// + https:/// + ftp://[:]@/ + tftp:/// + """ + request = dict.fromkeys(['protocol', 'host', 'file', 'user', 'passwd']) + protocols = ['scp', 'sftp', 'http', 'https', 'ftp', 'tftp'] + or_protocols = '|'.join(protocols) + + request_match = re.match(r'(' + or_protocols + r')://(.*?)(/.*)', + remote_file) + if request_match: + (request['protocol'], request['host'], + request['file']) = request_match.groups() + else: + print("Malformed URI") + sys.exit(1) + + user_match = re.search(r'(.*)@(.*)', request['host']) + if user_match: + request['user'] = user_match.groups()[0] + request['host'] = user_match.groups()[1] + passwd_match = re.search(r'(.*):(.*)', request['user']) + if passwd_match: + # Deprectated in RFC 3986, but maintain for backward compatability. + request['user'] = passwd_match.groups()[0] + request['passwd'] = passwd_match.groups()[1] + + remote_file = '{0}://{1}{2}'.format(request['protocol'], request['host'], request['file']) + + if request['protocol'] in ('scp', 'sftp'): + check_and_add_host_key(request['host']) + + if request['user'] and not request['passwd']: + curl_cmd = 'curl -# -u {0} {1}'.format(request['user'], remote_file) + else: + curl_cmd = 'curl -# {0}'.format(remote_file) + + config_file = None + try: + config_file = subprocess.check_output(curl_cmd, shell=True, + universal_newlines=True) + except subprocess.CalledProcessError as err: + print("Called process error: {}.".format(err)) + + return config_file -- cgit v1.2.3