Package x2go :: Module checkhosts

[frames] | no frames]

# Source Code for Module x2go.checkhosts

      1  # -*- coding: utf-8 -*-
      2
      3  # Copyright (C) 2010-2015 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, ECDSA...)
    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
    130 -    def get_key_fingerprint_with_colons(self):
    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