Ejemplo n.º 1
0
    def _connect(self):
        import paramiko  # Imported here due to relatively slow import
        self.__client = paramiko.SSHClient()
        self.__client.set_missing_host_key_policy(paramiko.client.AutoAddPolicy())
        self.__client.load_system_host_keys()

        try:
            self.__client.connect(self.host, username=self.username)
            self.__client_sftp = paramiko.SFTPClient.from_transport(self.__client.get_transport())
        except paramiko.SSHException as e:
            if len(e.args) == 1 and e.args[0] == 'No authentication methods available':
                raise DuctAuthenticationError(e.args[0])
            raise e
Ejemplo n.º 2
0
    def _connect(self):
        """
        The workflow to handle passwords and host keys used by this method is
        inspired by the `pxssh` module of `pexpect` (https://github.com/pexpect/pexpect).
        We have adjusted this workflow to our purposes.
        """
        import pexpect

        # Create socket directory if it doesn't exist.
        socket_dir = os.path.dirname(self._socket_path)
        if not os.path.exists(socket_dir):
            os.makedirs(socket_dir)
        # Create persistent master connection and exit.
        cmd = ''.join([
            "ssh {login} -MT ",
            "-S {socket} ",
            "-o ControlPersist=yes ",
            "-o StrictHostKeyChecking=no ",
            "-o UserKnownHostsFile=/dev/null " if not self.check_known_hosts else "",
            "-o NoHostAuthenticationForLocalhost=yes ",
            "-o ServerAliveInterval=60 ",
            "-o ServerAliveCountMax=2 ",
            "'exit'",
        ]).format(login=self._login_info, socket=self._socket_path)

        expected = [
            "WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!",    # 0
            "(?i)are you sure you want to continue connecting",    # 1
            "(?i)(?:(?:password)|(?:passphrase for key)):",        # 2
            "(?i)permission denied",                               # 3
            "(?i)terminal type",                                   # 4
            pexpect.TIMEOUT,                                       # 5
            "(?i)connection closed by remote host",                # 6
            "(?i)could not resolve hostname",                      # 7
            pexpect.EOF                                            # 8
        ]

        try:
            expect = pexpect.spawn(cmd)
            i = expect.expect(expected, timeout=10)

            # First phase
            if i == 0:  # If host identification changed, arrest any further attempts to connect
                error_message = (
                    'Host identification for {} has changed! This is most likely '
                    'due to the the server being redeployed or reconfigured but '
                    'may also be due to a man-in-the-middle attack. If you trust '
                    'your network connection, you should be safe to update the '
                    'host keys for this host. To do this manually, please remove '
                    'the line corresponding to this host in ~/.ssh/known_hosts; '
                    'or call the `update_host_keys` method of this client.'.format(self._host)
                )
                if self.interactive:
                    logger.error(error_message)
                    auto_fix = input('Would you like this client to do this for you? (y/n)')
                    if auto_fix == 'y':
                        self.update_host_keys()
                        return self.connect()
                    else:
                        raise RuntimeError("Host keys not updated. Please update keys manually.")
                else:
                    raise RuntimeError(error_message)
            if i == 1:  # Request to authorize host certificate (i.e. host not in the 'known_hosts' file)
                expect.sendline("yes")
                i = self.expect(expected)
            if i == 2:  # Request for password/passphrase
                expect.sendline(self.password or getpass.getpass('Password: '******'ascii')
                i = self.expect(expected)

            # Second phase
            if i == 1:  # Another request to authorize host certificate (i.e. host not in the 'known_hosts' file)
                raise RuntimeError('Received a second request to authorize host key. This should not have happened!')
            elif i in (2, 3):  # Second request for password/passphrase or rejection of credentials. For now, give up.
                raise DuctAuthenticationError('Invalid username and/or password, or private key is not unlocked.')
            elif i == 4:  # Another request for terminal type.
                raise RuntimeError('Received a second request for terminal type. This should not have happened!')
            elif i == 5:  # Timeout
                # In our instance, this means that we have not handled some or another aspect of the login procedure.
                # Since we are expecting an EOF when we have successfully logged in, hanging means that the SSH login
                # procedure is waiting for more information. Since we have no more to give, this means our login
                # was unsuccessful.
                raise RuntimeError('SSH client seems to be awaiting more information, but we have no more to give. The '
                                   'messages received so far are:\n{}'.format(expect.before))
            elif i == 6:  # Connection closed by remote host
                raise RuntimeError("Remote closed SSH connection")
            elif i == 7:
                raise RuntimeError("Cannot connect to {} on your current network connection".format(self.host))
        finally:
            expect.close()

        # We should be logged in at this point, but let us make doubly sure
        assert self.is_connected(), 'Unexpected failure to establish a connection with the remote host with command: \n ' \
                                    '{}\n\n Please report this!'.format(cmd)