Package x2go :: Module checkhosts
[frames] | no frames]

Source Code for Module x2go.checkhosts

  1  # -*- coding: utf-8 -*- 
  2   
  3  # Copyright (C) 2010-2013 by Mike Gabriel <mike.gabriel@das-netzwerkteam.de> 
  4  # 
  5  # Python X2Go is free software; you can redistribute it and/or modify 
  6  # it under the terms of the GNU Affero General Public License as published by 
  7  # the Free Software Foundation; either version 3 of the License, or 
  8  # (at your option) any later version. 
  9  # 
 10  # Python X2Go is distributed in the hope that it will be useful, 
 11  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 12  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 13  # GNU Affero General Public License for more details. 
 14  # 
 15  # You should have received a copy of the GNU Affero General Public License 
 16  # along with this program; if not, write to the 
 17  # Free Software Foundation, Inc., 
 18  # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. 
 19   
 20  """\ 
 21  Providing mechanisms to C{X2GoControlSession*} backends for checking host validity. 
 22   
 23  """ 
 24  __NAME__ = 'x2gocheckhosts-pylib' 
 25   
 26  # modules 
 27  import paramiko 
 28  import binascii 
 29   
 30  # Python X2Go modules 
 31  import sshproxy 
 32  import log 
 33  import x2go_exceptions 
 34  import random 
 35  import string 
 36   
37 -class X2GoMissingHostKeyPolicy(paramiko.MissingHostKeyPolicy):
38 """\ 39 Skeleton class for Python X2Go's missing host key policies. 40 41 """
42 - def __init__(self, caller=None, session_instance=None, fake_hostname=None):
43 """\ 44 @param caller: calling instance 45 @type caller: C{class} 46 @param session_instance: an X2Go session instance 47 @type session_instance: L{X2GoSession} instance 48 49 """ 50 self.caller = caller 51 self.session_instance = session_instance 52 self.fake_hostname = fake_hostname
53
54 - def get_client(self):
55 """\ 56 Retrieve the Paramiko SSH/Client. 57 58 @return: the associated X2Go control session instance. 59 @rtype: C{X2GoControlSession*} instance 60 61 """ 62 return self.client
63
64 - def get_hostname(self):
65 """\ 66 Retrieve the server hostname:port expression of the server to be validated. 67 68 @return: hostname:port 69 @rtype: C{str} 70 71 """ 72 return self.fake_hostname or self.hostname
73
74 - def get_hostname_name(self):
75 """\ 76 Retrieve the server hostname string of the server to be validated. 77 78 @return: hostname 79 @rtype: C{str} 80 81 """ 82 return self.get_hostname().split(':')[0].lstrip('[').rstrip(']')
83
84 - def get_hostname_port(self):
85 """\ 86 Retrieve the server port of the server to be validated. 87 88 @return: port 89 @rtype: C{str} 90 91 """ 92 return self.get_hostname().split(':')[1]
93
94 - def get_key(self):
95 """\ 96 Retrieve the host key of the server to be validated. 97 98 @return: host key 99 @rtype: Paramiko/SSH key instance 100 101 """ 102 return self.key
103
104 - def get_key_name(self):
105 """\ 106 Retrieve the host key name of the server to be validated. 107 108 @return: host key name (RSA, DSA, ...) 109 @rtype: C{str} 110 111 """ 112 return self.key.get_name().upper()
113
114 - def get_key_fingerprint(self):
115 """\ 116 Retrieve the host key fingerprint of the server to be validated. 117 118 @return: host key fingerprint 119 @rtype: C{str} 120 121 """ 122 return binascii.hexlify(self.key.get_fingerprint())
123
125 """\ 126 Retrieve the (colonized) host key fingerprint of the server 127 to be validated. 128 129 @return: host key fingerprint (with colons) 130 @rtype: C{str} 131 132 """ 133 _fingerprint = self.get_key_fingerprint() 134 _colon_fingerprint = '' 135 idx = 0 136 for char in _fingerprint: 137 idx += 1 138 _colon_fingerprint += char 139 if idx % 2 == 0: 140 _colon_fingerprint += ':' 141 return _colon_fingerprint.rstrip(':')
142 143
144 -class X2GoAutoAddPolicy(X2GoMissingHostKeyPolicy):
145
146 - def missing_host_key(self, client, hostname, key):
147 self.client = client 148 self.hostname = hostname 149 self.key = key 150 if self.session_instance and self.session_instance.control_session.unique_hostkey_aliases: 151 self.client._host_keys.add(self.session_instance.get_profile_id(), self.key.get_name(), self.key) 152 else: 153 self.client._host_keys.add(self.get_hostname(), self.key.get_name(), self.key) 154 if self.client._host_keys_filename is not None: 155 self.client.save_host_keys(self.client._host_keys_filename) 156 self.client._log(paramiko.common.DEBUG, 'Adding %s host key for %s: %s' % 157 (self.key.get_name(), self.get_hostname(), binascii.hexlify(self.key.get_fingerprint())))
158 159
160 -class X2GoInteractiveAddPolicy(X2GoMissingHostKeyPolicy):
161 """\ 162 Policy for making host key information available to Python X2Go after a 163 Paramiko/SSH connect has been attempted. This class needs information 164 about the associated L{X2GoSession} instance. 165 166 Once called, the L{missing_host_key} method of this class will try to call 167 L{X2GoSession.HOOK_check_host_dialog()}. This hook method---if not re-defined 168 in your application---will then try to call the L{X2GoClient.HOOK_check_host_dialog()}, 169 which then will return C{True} by default if not customized in your application. 170 171 To accept host key checks, make sure to either customize the 172 L{X2GoClient.HOOK_check_host_dialog()} method or the L{X2GoSession.HOOK_check_host_dialog()} 173 method and hook some interactive user dialog to either of them. 174 175 """
176 - def missing_host_key(self, client, hostname, key):
177 """\ 178 Handle a missing host key situation. This method calls 179 180 Once called, the L{missing_host_key} method will try to call 181 L{X2GoSession.HOOK_check_host_dialog()}. This hook method---if not re-defined 182 in your application---will then try to call the L{X2GoClient.HOOK_check_host_dialog()}, 183 which then will return C{True} by default if not customized in your application. 184 185 To accept host key checks, make sure to either customize the 186 L{X2GoClient.HOOK_check_host_dialog()} method or the L{X2GoSession.HOOK_check_host_dialog()} 187 method and hook some interactive user dialog to either of them. 188 189 @param client: SSH client (C{X2GoControlSession*}) instance 190 @type client: C{X2GoControlSession*} instance 191 @param hostname: remote hostname 192 @type hostname: C{str} 193 @param key: host key to validate 194 @type key: Paramiko/SSH key instance 195 196 @raise X2GoHostKeyException: if the X2Go server host key is not in the C{known_hosts} file 197 @raise X2GoSSHProxyHostKeyException: if the SSH proxy host key is not in the C{known_hosts} file 198 @raise SSHException: if this instance does not know its {self.session_instance} 199 200 """ 201 self.client = client 202 self.hostname = hostname 203 if (self.hostname.find(']') == -1) and (self.hostname.find(':') == -1): 204 # if hostname is an IPv4 quadruple with standard SSH port... 205 self.hostname = '[%s]:22' % self.hostname 206 self.key = key 207 self.client._log(paramiko.common.DEBUG, 'Interactively Checking %s host key for %s: %s' % 208 (self.key.get_name(), self.get_hostname(), binascii.hexlify(self.key.get_fingerprint()))) 209 if self.session_instance: 210 211 if self.fake_hostname is not None: 212 server_key = client.get_transport().get_remote_server_key() 213 keytype = server_key.get_name() 214 our_server_key = client._system_host_keys.get(self.fake_hostname, {}).get(keytype, None) 215 if our_server_key is None: 216 if self.session_instance.control_session.unique_hostkey_aliases: 217 our_server_key = client._host_keys.get(self.session_instance.get_profile_id(), {}).get(keytype, None) 218 if our_server_key is not None: 219 self.session_instance.logger('SSH host key verification for SSH-proxied host %s with %s fingerprint ,,%s\'\' succeeded. This host is known by the X2Go session profile ID of profile »%s«.' % (self.fake_hostname, self.get_key_name(), self.get_key_fingerprint_with_colons(), self.session_instance.profile_name), loglevel=log.loglevel_NOTICE) 220 return 221 else: 222 our_server_key = client._host_keys.get(self.fake_hostname, {}).get(keytype, None) 223 if our_server_key is not None: 224 self.session_instance.logger('SSH host key verification for SSH-proxied host %s with %s fingerprint ,,%s\'\' succeeded. This host is known by the address it has behind the SSH proxy host.' % (self.fake_hostname, self.get_key_name(), self.get_key_fingerprint_with_colons()), loglevel=log.loglevel_NOTICE) 225 return 226 227 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.' % (self.get_hostname(), self.get_key_name(), self.get_key_fingerprint_with_colons()), loglevel=log.loglevel_NOTICE) 228 _valid = self.session_instance.HOOK_check_host_dialog(self.get_hostname_name(), 229 port=self.get_hostname_port(), 230 fingerprint=self.get_key_fingerprint_with_colons(), 231 fingerprint_type=self.get_key_name(), 232 ) 233 if _valid: 234 if self.session_instance.control_session.unique_hostkey_aliases and type(self.caller) not in (sshproxy.X2GoSSHProxy, ): 235 paramiko.AutoAddPolicy().missing_host_key(client, self.session_instance.get_profile_id(), key) 236 else: 237 paramiko.AutoAddPolicy().missing_host_key(client, self.get_hostname(), key) 238 239 else: 240 if type(self.caller) in (sshproxy.X2GoSSHProxy, ): 241 raise x2go_exceptions.X2GoSSHProxyHostKeyException('Invalid host %s is not authorized for access. Add the host to Paramiko/SSH\'s known_hosts file.' % self.get_hostname()) 242 else: 243 raise x2go_exceptions.X2GoHostKeyException('Invalid host %s is not authorized for access. Add the host to Paramiko/SSH\'s known_hosts file.' % self.get_hostname()) 244 else: 245 raise x2go_exceptions.SSHException('Policy has collected host key information on %s for further introspection' % self.get_hostname())
246 247
248 -def check_ssh_host_key(x2go_sshclient_instance, hostname, port=22):
249 """\ 250 Perform a Paramiko/SSH host key check by connecting to the host and 251 validating the results (i.e. by validating raised exceptions during the 252 connect process). 253 254 @param x2go_sshclient_instance: a Paramiko/SSH client instance to be used for testing host key validity. 255 @type x2go_sshclient_instance: C{X2GoControlSession*} instance 256 @param hostname: hostname of server to validate 257 @type hostname: C{str} 258 @param port: port of server to validate 259 @type port: C{int} 260 261 @return: returns a tuple with the following components (<host_ok>, <hostname>, <port>, <fingerprint>, <fingerprint_type>) 262 @rtype: C{tuple} 263 264 @raise SSHException: if an SSH exception occurred, that we did not provocate in L{X2GoInteractiveAddPolicy.missing_host_key()} 265 266 """ 267 _hostname = hostname 268 _port = port 269 _fingerprint = 'NO-FINGERPRINT' 270 _fingerprint_type = 'SOME-KEY-TYPE' 271 272 _check_policy = X2GoInteractiveAddPolicy() 273 x2go_sshclient_instance.set_missing_host_key_policy(_check_policy) 274 275 host_ok = False 276 try: 277 paramiko.SSHClient.connect(x2go_sshclient_instance, hostname=hostname, port=port, username='foo', password="".join([random.choice(string.letters+string.digits) for x in range(1, 20)])) 278 except x2go_exceptions.AuthenticationException: 279 host_ok = True 280 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) 281 except x2go_exceptions.SSHException, e: 282 msg = str(e) 283 if msg.startswith('Policy has collected host key information on '): 284 _hostname = _check_policy.get_hostname().split(':')[0].lstrip('[').rstrip(']') 285 _port = _check_policy.get_hostname().split(':')[1] 286 _fingerprint = _check_policy.get_key_fingerprint_with_colons() 287 _fingerprint_type = _check_policy.get_key_name() 288 else: 289 raise(e) 290 x2go_sshclient_instance.set_missing_host_key_policy(paramiko.RejectPolicy()) 291 except: 292 # let any other error be handled by subsequent algorithms 293 pass 294 295 return (host_ok, _hostname, _port, _fingerprint, _fingerprint_type)
296