The branch, build-baikal has been updated via 2be37b5636f7867e4e692d9337bd986ba8a1393e (commit) from 88a3386e5db21c608da8dd382c268912ecc57b37 (commit) Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below. - Log ----------------------------------------------------------------- ----------------------------------------------------------------------- Summary of changes: x2go/backends/control/_stdout.py | 68 ++++++++++++++++---------------------- x2go/checkhosts.py | 68 ++++++++++++++++++++++++++++++++++++-- x2go/registry.py | 15 +++++---- x2go/session.py | 5 ++- x2go/sshproxy.py | 30 ++++++++++++++--- x2go/x2go_exceptions.py | 2 ++ 6 files changed, 134 insertions(+), 54 deletions(-) The diff of changes is: diff --git a/x2go/backends/control/_stdout.py b/x2go/backends/control/_stdout.py index a07c3f5..7490809 100644 --- a/x2go/backends/control/_stdout.py +++ b/x2go/backends/control/_stdout.py @@ -34,6 +34,9 @@ import gevent import copy import binascii +import string +import random + # Python X2go modules import x2go.sshproxy as sshproxy import x2go.log as log @@ -238,41 +241,16 @@ class X2goControlSessionSTDOUT(paramiko.SSHClient): def check_host(self, hostname, port=22): """\ - Perform a Paramiko/SSH host key check. + Wraps around a Paramiko/SSH host key check. """ - _hostname = hostname - _port = port - _fingerprint = 'NO-FINGERPRINT' - _fingerprint_type = 'SOME-KEY-TYPE' - - _check_policy = checkhosts.X2goCheckHostKeyPolicy() - self.set_missing_host_key_policy(_check_policy) - - try: - paramiko.SSHClient.connect(self, hostname=hostname, port=port, username='foo', password='bar') - except x2go_exceptions.AuthenticationException: - host_ok = True - self.logger('SSH host key verification for host [%s]:%s succeeded. Host is already known to the client\'s Paramiko/SSH sub-system.' % (_hostname, _port), loglevel=log.loglevel_NOTICE) - except x2go_exceptions.SSHException, e: - msg = str(e) - if msg.startswith('Checked host key for X2go server '): - host_ok = False - _hostname = _check_policy.get_hostname().split(':')[0].lstrip('[').rstrip(']') - _port = _check_policy.get_hostname().split(':')[1] - _fingerprint = _check_policy.get_key_fingerprint_with_colons() - _fingerprint_type = _check_policy.get_key_name() - self.logger('SSH host key verification for host [%s]:%s with %s fingerprint ,,%s\'\' failed. Seeing this X2go server for the first time.' % (_hostname, _port, _fingerprint_type, _fingerprint), loglevel=log.loglevel_NOTICE) - else: - raise(e) - - self.set_missing_host_key_policy(paramiko.RejectPolicy()) - return (host_ok, _hostname, _port, _fingerprint, _fingerprint_type) + return checkhosts.check_ssh_host_key(self, hostname, port=port) def connect(self, hostname, port=22, username='', password='', pkey=None, use_sshproxy=False, sshproxy_host='', sshproxy_user='', sshproxy_password='', sshproxy_key_filename='', sshproxy_tunnel='', key_filename=None, timeout=None, allow_agent=False, look_for_keys=False, + session_instance=None, add_to_known_hosts=False, force_password_auth=False): """\ Connect to an X2go server and authenticate to it. This method is directly @@ -326,6 +304,9 @@ class X2goControlSessionSTDOUT(paramiko.SSHClient): @param force_password_auth: non-paramiko option, disable pub/priv key authentication completely, even if the C{pkey} or the C{key_filename} parameter is given @type force_password_auth: C{bool} + @param session_instance: an instance L{X2goSession} using this L{X2goControlSessionSTDOUT} + instance. + @type session_instance: C{instance} @raise BadHostKeyException: if the server's host key could not be verified @@ -337,14 +318,13 @@ class X2goControlSessionSTDOUT(paramiko.SSHClient): """ if use_sshproxy and sshproxy_host and sshproxy_user: try: - add_to_known_hosts = add_to_known_hosts | self.add_to_known_hosts - self.sshproxy_session = sshproxy.X2goSSHProxy(add_to_known_hosts=add_to_known_hosts, - known_hosts=self.known_hosts, + self.sshproxy_session = sshproxy.X2goSSHProxy(known_hosts=self.known_hosts, sshproxy_host=sshproxy_host, sshproxy_user=sshproxy_user, sshproxy_password=sshproxy_password, sshproxy_key_filename=sshproxy_key_filename, sshproxy_tunnel=sshproxy_tunnel, + session_instance=session_instance, logger=self.logger, ) @@ -364,6 +344,10 @@ class X2goControlSessionSTDOUT(paramiko.SSHClient): gevent.sleep(1) port = self.sshproxy_session.get_local_proxy_port() + + if not add_to_known_hosts and session_instance: + self.set_missing_host_key_policy(checkhosts.X2goInteractiveAddPolicy(caller=self, session_instance=session_instance)) + if add_to_known_hosts: self.set_missing_host_key_policy(paramiko.AutoAddPolicy()) @@ -374,6 +358,9 @@ class X2goControlSessionSTDOUT(paramiko.SSHClient): self.logger('connecting to %s' % hostname, loglevel=log.loglevel_NOTICE) + if self.known_hosts is not None: + utils.touch_file(self.known_hosts) + self.load_host_keys(self.known_hosts) if (key_filename and os.path.exists(os.path.normpath(key_filename))) or pkey: try: self.logger('trying SSH pub/priv key authentication with server', loglevel=log.loglevel_DEBUG) @@ -399,8 +386,11 @@ class X2goControlSessionSTDOUT(paramiko.SSHClient): self.close() raise(e) - # if there is not private key, we will use the given password - elif password: + # if there is not private key, we will use the given password, if any + else: + # create a random password if password is empty to trigger host key validity check + if not password: + password = "".join([random.choice(string.letters+string.digits) for x in range(1, 20)]) self.logger('performing SSH keyboard-interactive authentication with server', loglevel=log.loglevel_DEBUG) try: paramiko.SSHClient.connect(self, hostname, port=port, username=username, password=password, @@ -410,13 +400,11 @@ class X2goControlSessionSTDOUT(paramiko.SSHClient): self.sshproxy_session.stop_thread() self.close() raise(e) - - # authentication failed - else: - if self.sshproxy_session: - self.sshproxy_session.stop_thread() - self.close() - raise paramiko.AuthenticationException() + except x2go_exceptions.X2goHostKeyException, e: + if self.sshproxy_session: + self.sshproxy_session.stop_thread() + self.close() + raise(e) self.set_missing_host_key_policy(paramiko.RejectPolicy()) diff --git a/x2go/checkhosts.py b/x2go/checkhosts.py index d4eda9a..2bdcc4b 100644 --- a/x2go/checkhosts.py +++ b/x2go/checkhosts.py @@ -28,9 +28,11 @@ import paramiko import binascii # Python X2go modules +import sshproxy +import log import x2go_exceptions -class X2goCheckHostKeyPolicy(paramiko.MissingHostKeyPolicy): +class X2goInteractiveAddPolicy(paramiko.MissingHostKeyPolicy): """\ Policy for making host key information available to Python X2go after a Paramiko/SSH connect has been attempted. A connect that uses this @@ -38,13 +40,32 @@ class X2goCheckHostKeyPolicy(paramiko.MissingHostKeyPolicy): This is used by L{X2goControlSessionSTDOUT}. """ + def __init__(self, caller=None, session_instance=None): + self.caller = caller + self.session_instance = session_instance + def missing_host_key(self, client, hostname, key): self.client = client self.hostname = hostname self.key = key - client._log(paramiko.common.DEBUG, 'Checking %s host key for %s: %s' % + client._log(paramiko.common.DEBUG, 'Interactively Checking %s host key for %s: %s' % (key.get_name(), hostname, binascii.hexlify(key.get_fingerprint()))) - raise x2go_exceptions.SSHException('Checked host key for X2go server %s' % hostname) + if self.session_instance: + self.session_instance.logger('SSH host key verification for host %s with %s fingerprint ,,%s\'\' initiated. We are seeing this X2go server for the first time.' % (hostname, self.get_key_name(), self.get_key_fingerprint_with_colons()), loglevel=log.loglevel_NOTICE) + _valid = self.session_instance.HOOK_check_host_dialog(self.get_hostname_name(), + port=self.get_hostname_port(), + fingerprint=self.get_key_fingerprint_with_colons(), + fingerprint_type=self.get_key_name(), + ) + if _valid: + paramiko.AutoAddPolicy().missing_host_key(client, hostname, key) + else: + if type(self.caller) in (sshproxy.X2goSSHProxy, ): + raise x2go_exceptions.X2goSSHProxyHostKeyException('Invalid host %s is not authorized for access. Add the host to Paramiko/SSH\'s known_hosts file.' % hostname) + else: + raise x2go_exceptions.X2goHostKeyException('Invalid host %s is not authorized for access. Add the host to Paramiko/SSH\'s known_hosts file.' % hostname) + else: + raise x2go_exceptions.SSHException('Policy has collected host key information on %s for further introspection' % hostname) def get_client(self): return self.client @@ -52,6 +73,12 @@ class X2goCheckHostKeyPolicy(paramiko.MissingHostKeyPolicy): def get_hostname(self): return self.hostname + def get_hostname_name(self): + return self.get_hostname().split(':')[0].lstrip('[').rstrip(']') + + def get_hostname_port(self): + return self.get_hostname().split(':')[1] + def get_key(self): return self.key @@ -72,3 +99,38 @@ class X2goCheckHostKeyPolicy(paramiko.MissingHostKeyPolicy): _colon_fingerprint += ':' return _colon_fingerprint.rstrip(':') + +def check_ssh_host_key(x2go_sshclient_instance, hostname, port=22): + """\ + Perform a Paramiko/SSH host key check. + + """ + _hostname = hostname + _port = port + _fingerprint = 'NO-FINGERPRINT' + _fingerprint_type = 'SOME-KEY-TYPE' + + _check_policy = X2goInteractiveAddPolicy() + x2go_sshclient_instance.set_missing_host_key_policy(_check_policy) + + host_ok = False + try: + paramiko.SSHClient.connect(x2go_sshclient_instance, hostname=hostname, port=port, username='foo', password='bar') + except x2go_exceptions.AuthenticationException: + host_ok = True + x2go_sshclient_instance.logger('SSH host key verification for host [%s]:%s succeeded. Host is already known to the client\'s Paramiko/SSH sub-system.' % (_hostname, _port), loglevel=log.loglevel_NOTICE) + except x2go_exceptions.SSHException, e: + msg = str(e) + if msg.startswith('Policy has collected host key information on '): + _hostname = _check_policy.get_hostname().split(':')[0].lstrip('[').rstrip(']') + _port = _check_policy.get_hostname().split(':')[1] + _fingerprint = _check_policy.get_key_fingerprint_with_colons() + _fingerprint_type = _check_policy.get_key_name() + else: + raise(e) + x2go_sshclient_instance.set_missing_host_key_policy(paramiko.RejectPolicy()) + except: + # let any other error be handled by subsequent algorithms + pass + + return (host_ok, _hostname, _port, _fingerprint, _fingerprint_type) diff --git a/x2go/registry.py b/x2go/registry.py index 7a17017..8d134e7 100644 --- a/x2go/registry.py +++ b/x2go/registry.py @@ -71,6 +71,9 @@ class X2goSessionRegistry(object): self.registry = {} self.control_sessions = {} + def keys(self): + return self.registry.keys() + def __repr__(self): result = 'X2goSessionRegistry(' for p in dir(self): @@ -118,13 +121,13 @@ class X2goSessionRegistry(object): _session_summary['control_params'] = _r and self(session_uuid).control_params or {} _session_summary['terminal_session'] = _r and self(session_uuid).get_terminal_session() or None _session_summary['terminal_params'] = _r and self(session_uuid).terminal_params or {} - _session_summary['active_threads'] = _r and self(session_uuid).get_terminal_session().active_threads or [] + _session_summary['active_threads'] = _r and bool(self(session_uuid).get_terminal_session()) and self(session_uuid).get_terminal_session().active_threads or [] _session_summary['backends'] = { - 'control': _r and self(session_uuid)._control_backend or None, - 'terminal': _r and self(session_uuid)._terminal_backend or None, - 'info': _r and self(session_uuid)._info_backend or None, - 'list': _r and self(session_uuid)._list_backend or None, - 'proxy': _r and self(session_uuid)._proxy_backend or None, + 'control': _r and self(session_uuid).control_backend or None, + 'terminal': _r and self(session_uuid).terminal_backend or None, + 'info': _r and self(session_uuid).info_backend or None, + 'list': _r and self(session_uuid).list_backend or None, + 'proxy': _r and self(session_uuid).proxy_backend or None, } if _r: diff --git a/x2go/session.py b/x2go/session.py index d65226f..c225c60 100644 --- a/x2go/session.py +++ b/x2go/session.py @@ -525,7 +525,10 @@ class X2goSession(object): _params.update(self.control_params) _params.update(self.sshproxy_params) - self.connected = self.control_session.connect(self.server, use_sshproxy=self.use_sshproxy, **_params) + self.connected = self.control_session.connect(self.server, + use_sshproxy=self.use_sshproxy, + session_instance=self, + **_params) # remove credentials immediately self.control_params['password'] = '' try: del self.control_params['sshproxy_user'] diff --git a/x2go/sshproxy.py b/x2go/sshproxy.py index 69cdce7..7ef8e63 100644 --- a/x2go/sshproxy.py +++ b/x2go/sshproxy.py @@ -31,8 +31,12 @@ import paramiko import threading import socket +import string +import random + # Python X2go modules import x2go.forward as forward +import x2go.checkhosts as checkhosts import x2go.log as log import x2go.utils as utils from x2go.x2go_exceptions import * @@ -56,6 +60,7 @@ class X2goSSHProxy(paramiko.SSHClient, threading.Thread): sshproxy_password=None, sshproxy_key_filename=None, sshproxy_tunnel=None, ssh_rootdir=os.path.join(_LOCAL_HOME, _X2GO_SSH_ROOTDIR), + session_instance=None, logger=None, loglevel=log.loglevel_DEFAULT, ): """\ STILL UNDOCUMENTED @@ -77,7 +82,7 @@ class X2goSSHProxy(paramiko.SSHClient, threading.Thread): self.hostname, self.port, self.username = hostname, port, username # translate between X2goSession options and paramiko.SSHCLient.connect() options - if sshproxy_host: + if sshproxy_host: if sshproxy_host.find(':'): self.hostname = sshproxy_host.split(':')[0] try: self.port = int(sshproxy_host.split(':')[1]) @@ -109,6 +114,9 @@ class X2goSSHProxy(paramiko.SSHClient, threading.Thread): utils.touch_file(known_hosts) self.load_host_keys(known_hosts) + if not add_to_known_hosts and session_instance: + self.set_missing_host_key_policy(checkhosts.X2goInteractiveAddPolicy(caller=self, session_instance=session_instance)) + if add_to_known_hosts: self.set_missing_host_key_policy(paramiko.AutoAddPolicy()) @@ -124,7 +132,11 @@ class X2goSSHProxy(paramiko.SSHClient, threading.Thread): except AuthenticationException, e: raise X2goSSHProxyAuthenticationException('pubkey auth mechanisms both failed') - elif password: + # if there is not private key, we will use the given password, if any + else: + # create a random password if password is empty to trigger host key validity check + if not password: + password = "".join([random.choice(string.letters+string.digits) for x in range(1, 20)]) try: self.connect(self.hostname, port=self.port, username=self.username, @@ -134,8 +146,7 @@ class X2goSSHProxy(paramiko.SSHClient, threading.Thread): ) except AuthenticationException: raise X2goSSHProxyAuthenticationException('interactive auth mechanisms failed') - else: - raise X2goSSHProxyAuthenticationException('no auth mechanism available') + except paramiko.SSHException, e: raise X2goSSHProxyException(str(e)) @@ -143,6 +154,17 @@ class X2goSSHProxy(paramiko.SSHClient, threading.Thread): threading.Thread.__init__(self) self.daemon = True + def check_host(self): + """\ + Wraps around a Paramiko/SSH host key check. + + """ + _valid = False + (_valid, _hostname, _port, _fingerprint, _fingerprint_type) = checkhosts.check_ssh_host_key(self, self.hostname, port=self.port) + if not _valid and self.session_instance: + _valid = self.session_instance.HOOK_check_host_dialog(_hostname, _port, fingerprint=_fingerprint, fingerprint_type=_fingerprint_type) + return _valid + def run(self): if self.get_transport() is not None and self.get_transport().is_authenticated(): diff --git a/x2go/x2go_exceptions.py b/x2go/x2go_exceptions.py index 73faf76..22a98fb 100644 --- a/x2go/x2go_exceptions.py +++ b/x2go/x2go_exceptions.py @@ -43,6 +43,8 @@ class _X2goException(exceptions.BaseException): pass class X2goClientException(_X2goException): pass class X2goSessionException(_X2goException): pass class X2goControlSessionException(_X2goException): pass +class X2goHostKeyException(_X2goException): pass +class X2goSSHProxyHostKeyException(_X2goException): pass class X2goTerminalSessionException(_X2goException): pass class X2goSessionCacheException(_X2goException): pass class X2goUserException(_X2goException): pass hooks/post-receive -- python-x2go.git (Python X2Go Client API) This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "python-x2go.git" (Python X2Go Client API).