summaryrefslogtreecommitdiff
path: root/python/vyos/remote.py
diff options
context:
space:
mode:
authorJohn Estabrook <jestabro@sentrium.io>2019-05-23 07:38:54 -0500
committerJohn Estabrook <jestabro@sentrium.io>2019-05-28 10:00:43 -0500
commitdf231744b98202ec5fbcd236e795df7399747a0e (patch)
tree8fdaf63146872172e7845062fe04e43b397d7649 /python/vyos/remote.py
parentbf0f721432fa05bbc7058a0b43e2acf4ad1f30e3 (diff)
downloadvyos-1x-df231744b98202ec5fbcd236e795df7399747a0e.tar.gz
vyos-1x-df231744b98202ec5fbcd236e795df7399747a0e.zip
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.
Diffstat (limited to 'python/vyos/remote.py')
-rw-r--r--python/vyos/remote.py133
1 files changed, 133 insertions, 0 deletions
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 <maintainers@vyos.io>
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+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://<user>[:<passwd>]@<host>/<file>
+ sftp://<user>[:<passwd>]@<host>/<file>
+ http://<host>/<file>
+ https://<host>/<file>
+ ftp://<user>[:<passwd>]@<host>/<file>
+ tftp://<host>/<file>
+ """
+ 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