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

Source Code for Module x2go.checkhosts

  1  # -*- coding: utf-8 -*- 
  2   
  3  # Copyright (C) 2010-2014 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 if ":" in self.get_hostname(): 83 return self.get_hostname().split(':')[0].lstrip('[').rstrip(']') 84 else: 85 return self.get_hostname().lstrip('[').rstrip(']')
86
87 - def get_hostname_port(self):
88 """\ 89 Retrieve the server port of the server to be validated. 90 91 @return: port 92 @rtype: C{str} 93 94 """ 95 if ":" in self.get_hostname(): 96 return int(self.get_hostname().split(':')[1]) 97 else: 98 return 22
99
100 - def get_key(self):
101 """\ 102 Retrieve the host key of the server to be validated. 103 104 @return: host key 105 @rtype: Paramiko/SSH key instance 106 107 """ 108 return self.key
109
110 - def get_key_name(self):
111 """\ 112 Retrieve the host key name of the server to be validated. 113 114 @return: host key name (RSA, DSA, ...) 115 @rtype: C{str} 116 117 """ 118 return self.key.get_name().upper()
119
120 - def get_key_fingerprint(self):
121 """\ 122 Retrieve the host key fingerprint of the server to be validated. 123 124 @return: host key fingerprint 125 @rtype: C{str} 126 127 """ 128 return binascii.hexlify(self.key.get_fingerprint())
129
131 """\ 132 Retrieve the (colonized) host key fingerprint of the server 133 to be validated. 134 135 @return: host key fingerprint (with colons) 136 @rtype: C{str} 137 138 """ 139 _fingerprint = self.get_key_fingerprint() 140 _colon_fingerprint = '' 141 idx = 0 142 for char in _fingerprint: 143 idx += 1 144 _colon_fingerprint += char 145 if idx % 2 == 0: 146 _colon_fingerprint += ':' 147 return _colon_fingerprint.rstrip(':')
148 149
150 -class X2GoAutoAddPolicy(X2GoMissingHostKeyPolicy):
151
152 - def missing_host_key(self, client, hostname, key):
153 self.client = client 154 self.hostname = hostname 155 self.key = key 156 if self.session_instance and self.session_instance.control_session.unique_hostkey_aliases: 157 self.client._host_keys.add(self.session_instance.get_profile_id(), self.key.get_name(), self.key) 158 else: 159 self.client._host_keys.add(self.get_hostname(), self.key.get_name(), self.key) 160 if self.client._host_keys_filename is not None: 161 self.client.save_host_keys(self.client._host_keys_filename) 162 self.client._log(paramiko.common.DEBUG, 'Adding %s host key for %s: %s' % 163 (self.key.get_name(), self.get_hostname(), binascii.hexlify(self.key.get_fingerprint())))
164 165
166 -class X2GoInteractiveAddPolicy(X2GoMissingHostKeyPolicy):
167 """\ 168 Policy for making host key information available to Python X2Go after a 169 Paramiko/SSH connect has been attempted. This class needs information 170 about the associated L{X2GoSession} instance. 171 172 Once called, the L{missing_host_key} method of this class will try to call 173 L{X2GoSession.HOOK_check_host_dialog()}. This hook method---if not re-defined 174 in your application---will then try to call the L{X2GoClient.HOOK_check_host_dialog()}, 175 which then will return C{True} by default if not customized in your application. 176 177 To accept host key checks, make sure to either customize the 178 L{X2GoClient.HOOK_check_host_dialog()} method or the L{X2GoSession.HOOK_check_host_dialog()} 179 method and hook some interactive user dialog to either of them. 180 181 """
182 - def missing_host_key(self, client, hostname, key):
183 """\ 184 Handle a missing host key situation. This method calls 185 186 Once called, the L{missing_host_key} method will try to call 187 L{X2GoSession.HOOK_check_host_dialog()}. This hook method---if not re-defined 188 in your application---will then try to call the L{X2GoClient.HOOK_check_host_dialog()}, 189 which then will return C{True} by default if not customized in your application. 190 191 To accept host key checks, make sure to either customize the 192 L{X2GoClient.HOOK_check_host_dialog()} method or the L{X2GoSession.HOOK_check_host_dialog()} 193 method and hook some interactive user dialog to either of them. 194 195 @param client: SSH client (C{X2GoControlSession*}) instance 196 @type client: C{X2GoControlSession*} instance 197 @param hostname: remote hostname 198 @type hostname: C{str} 199 @param key: host key to validate 200 @type key: Paramiko/SSH key instance 201 202 @raise X2GoHostKeyException: if the X2Go server host key is not in the C{known_hosts} file 203 @raise X2GoSSHProxyHostKeyException: if the SSH proxy host key is not in the C{known_hosts} file 204 @raise SSHException: if this instance does not know its {self.session_instance} 205 206 """ 207 self.client = client 208 self.hostname = hostname 209 if (self.hostname.find(']') == -1) and (self.hostname.find(':') == -1): 210 # if hostname is an IPv4 quadruple with standard SSH port... 211 self.hostname = '[%s]:22' % self.hostname 212 self.key = key 213 self.client._log(paramiko.common.DEBUG, 'Interactively Checking %s host key for %s: %s' % 214 (self.key.get_name(), self.get_hostname(), binascii.hexlify(self.key.get_fingerprint()))) 215 if self.session_instance: 216 217 if self.fake_hostname is not None: 218 server_key = client.get_transport().get_remote_server_key() 219 keytype = server_key.get_name() 220 our_server_key = client._system_host_keys.get(self.fake_hostname, {}).get(keytype, None) 221 if our_server_key is None: 222 if self.session_instance.control_session.unique_hostkey_aliases: 223 our_server_key = client._host_keys.get(self.session_instance.get_profile_id(), {}).get(keytype, None) 224 if our_server_key is not None: 225 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) 226 return 227 else: 228 our_server_key = client._host_keys.get(self.fake_hostname, {}).get(keytype, None) 229 if our_server_key is not None: 230 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) 231 return 232 233 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) 234 _valid = self.session_instance.HOOK_check_host_dialog(self.get_hostname_name(), 235 port=self.get_hostname_port(), 236 fingerprint=self.get_key_fingerprint_with_colons(), 237 fingerprint_type=self.get_key_name(), 238 ) 239 if _valid: 240 if self.session_instance.control_session.unique_hostkey_aliases and type(self.caller) not in (sshproxy.X2GoSSHProxy, ): 241 paramiko.AutoAddPolicy().missing_host_key(client, self.session_instance.get_profile_id(), key) 242 else: 243 paramiko.AutoAddPolicy().missing_host_key(client, self.get_hostname(), key) 244 245 else: 246 if type(self.caller) in (sshproxy.X2GoSSHProxy, ): 247 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()) 248 else: 249 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()) 250 else: 251 raise x2go_exceptions.SSHException('Policy has collected host key information on %s for further introspection' % self.get_hostname())
252 253
254 -def check_ssh_host_key(x2go_sshclient_instance, hostname, port=22):
255 """\ 256 Perform a Paramiko/SSH host key check by connecting to the host and 257 validating the results (i.e. by validating raised exceptions during the 258 connect process). 259 260 @param x2go_sshclient_instance: a Paramiko/SSH client instance to be used for testing host key validity. 261 @type x2go_sshclient_instance: C{X2GoControlSession*} instance 262 @param hostname: hostname of server to validate 263 @type hostname: C{str} 264 @param port: port of server to validate 265 @type port: C{int} 266 267 @return: returns a tuple with the following components (<host_ok>, <hostname>, <port>, <fingerprint>, <fingerprint_type>) 268 @rtype: C{tuple} 269 270 @raise SSHException: if an SSH exception occurred, that we did not provocate in L{X2GoInteractiveAddPolicy.missing_host_key()} 271 272 """ 273 _hostname = hostname 274 _port = port 275 _fingerprint = 'NO-FINGERPRINT' 276 _fingerprint_type = 'SOME-KEY-TYPE' 277 278 _check_policy = X2GoInteractiveAddPolicy() 279 x2go_sshclient_instance.set_missing_host_key_policy(_check_policy) 280 281 host_ok = False 282 try: 283 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)])) 284 except x2go_exceptions.AuthenticationException: 285 host_ok = True 286 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) 287 except x2go_exceptions.SSHException, e: 288 msg = str(e) 289 if msg.startswith('Policy has collected host key information on '): 290 _hostname = _check_policy.get_hostname().split(':')[0].lstrip('[').rstrip(']') 291 _port = _check_policy.get_hostname().split(':')[1] 292 _fingerprint = _check_policy.get_key_fingerprint_with_colons() 293 _fingerprint_type = _check_policy.get_key_name() 294 else: 295 raise(e) 296 x2go_sshclient_instance.set_missing_host_key_policy(paramiko.RejectPolicy()) 297 except: 298 # let any other error be handled by subsequent algorithms 299 pass 300 301 return (host_ok, _hostname, _port, _fingerprint, _fingerprint_type)
302