Example #1
0
    def Connect(self,
                username,
                password=None,
                ssh_keys=None,
                enable_password=None,
                ssl_cert_set=None):
        """Sets up a connection to the device.

    Concrete classes must implement _Connect() instead, with the same arguments.

    Concrete classes are expected not to disconnect the connection until it
    is cleaned-up by Disconnect().  A generic exception handler at the top-
    level should ensure sessions have an opportunity to be cleaned-up upon
    abnormal program termination.

    Args:
      username: A string, the username (role account) to use.
      password: A string, the password to use (optional; may be None).
      ssh_keys: A tuple of strings, SSH private keys (optional; may be None).
      enable_password: A string, an optional enable password (may be None).
      ssl_cert_set: An optional SSLCertificateSet protobuf (may be None).

    Raises:
      exceptions.ConnectError: the connection could not be established.
      exceptions.AuthenticationError: A device authentication error occurred, or
        neither a password nor an SSH private key was supplied.
    """
        # Either an SSH key or password must be supplied for authentication.
        if (password is None and not ssh_keys and not ssl_cert_set
                and not FLAGS.use_ssh_agent):
            raise exceptions.AuthenticationError(
                'Cannot connect. No authentication information provided to device '
                'Connect method.')

        self._username = username
        self._password = password
        self._ssh_keys = ssh_keys or ()
        self._enable_password = enable_password
        self._ssl_cert_set = ssl_cert_set

        if not self.loopback_ipv4 and not self.accessproxy_device_dict:
            raise exceptions.ConnectError(
                'Device %r, or any access proxies, need to have an IPv4 '
                'management address.' % self.host)

        logging.debug('In BaseDevice.Connect, host is %s, _connected is %s',
                      self.host, self._connected)
        while not self.connected:
            try:
                if self._host_status:
                    logging.debug('CONNECTING %s(%s)', self.host,
                                  self.loopback_ipv4)
                    self._Connect(username,
                                  password=password,
                                  ssh_keys=self._ssh_keys,
                                  enable_password=enable_password,
                                  ssl_cert_set=ssl_cert_set)
                    self.connected = True
                    logging.debug('CONNECTED %s(%s)', self.host,
                                  self.loopback_ipv4)
                    self._last_failure_time = None
                else:
                    self._HostDownPrepareConnect()
            except (exceptions.ConnectError,
                    exceptions.AuthenticationError), e:
                logging.error('CONNECT FAILURE %s(%s)', self.host,
                              self.loopback_ipv4)
                self._host_status = False
                self.__exc = e
                raise
Example #2
0
class BrocadeMlxDevice(BrocadeDevice):
    """A base device model suitable for Brocade MLX devices.

  See the base_device.BaseDevice method docstrings.
  """

    disable_pager_command = _BROCADE_MLX_DISABLE_PAGER

    def __init__(self, **kwargs):
        self.vendor_name = 'brocademlx'
        super(BrocadeMlxDevice, self).__init__(**kwargs)

    def _GetFileSize(self, file_name, data):
        """Gets the size of a file in Brocade 'dir' output.

    Args:
      file_name: A string, the file name.
      data: A string, the Brocade's "dir" output.

    Returns:
      An int, the file size, or None if the value could not be determined.
    """
        for line in data.splitlines():
            match = RE_FILE_LISTING.match(line)
            if match is not None:
                (file_size, fname) = match.groups()
                for char in string.punctuation:
                    file_size = file_size.replace(char, '')
                if file_name.strip() == fname.strip():
                    try:
                        return int(file_size)
                    except ValueError:
                        continue
        return None

    def _SetConfig(self, destination_file, data, canary):
        # Canarying is not supported on BROCADE.
        if canary:
            raise exceptions.SetConfigCanaryingError(
                '%s devices do not support '
                'configuration canarying.' % self.vendor_name)
        # The result object.
        result = base_device.SetConfigResult()
        # Check for a connection to the Brocade.
        if not self._GetConnected():
            raise exceptions.SetConfigError('Cannot use unless already '
                                            'connected to the device.')

        if destination_file in self.NON_FILE_DESTINATIONS:
            # Use a random remote file name
            file_name = 'push.%s' % os.urandom(8).encode('hex')
        else:
            # Okay, the user is just copying a file, not a configuraiton into either
            # startup-config or running-config, therefore we should use the entire
            # path.
            file_name = destination_file

        # Copy the file to the router using SCP.
        scp = pexpect_connection.ScpPutConnection(host=self.loopback_ipv4,
                                                  username=self._username,
                                                  password=self._password)

        # This is a workaround. Brocade case: 537017.
        # Brocade changed all the filename to lowercases after scp
        file_name = file_name.lower()
        try:
            scp.Copy(data, destination_file='slot1:' + file_name)
        except pexpect_connection.Error, e:
            raise exceptions.SetConfigError(
                'Failed to copy configuration to remote device. %s' % str(e))
        # Now that everything is OK locally and the file has been copied,
        # check the file and tell the device to set the new configuration.
        try:
            # Get the file size on the Brocade.
            try:
                cmd = 'dir /slot1/%s' % file_name
                dir_output = self._Cmd(cmd)
            except exceptions.CmdError, e:
                if 'Invalid input at' in str(e):
                    raise exceptions.AuthenticationError(
                        'Username/password for %s(%s) has insufficient privileges '
                        'to set configuration.' %
                        (self.host, self.loopback_ipv4))
                else:
                    raise exceptions.SetConfigError(
                        'Could not traverse directory '
                        'output.  Command was: %r. '
                        'Error: %r' % (cmd, str(e)))
            destination_file_size = self._GetFileSize(file_name, dir_output)
            # We couldn't parse the output for some reason.
            if destination_file_size is None:
                raise exceptions.SetConfigError(
                    'Could not find or parse remote '
                    'file size after copy to device.')

            # Verify file is the correct size on the Brocade.
            # This should use a checksum (e.g. MD5 or SHA1); Brocade case: 609719.
            if destination_file_size != len(data):
                raise exceptions.SetConfigError(
                    'File transfer corrupted. Source file was: %d bytes, '
                    'Destination file was: %d bytes.' %
                    (len(data), destination_file_size))

            # Copy the file from flash to the
            # destination(running-config, startup-config)
            if destination_file == self.CONFIG_STARTUP:
                try:
                    self._connection.child.send(
                        'copy slot1 startup-config %s\r' % file_name)
                    time.sleep(MINOR_PAUSE)
                    pindex = self._connection.child.expect(
                        ['Total bytes', self._connection.re_prompt, 'Error'],
                        timeout=self.timeout_act_user)
                    if pindex == 2:
                        raise exceptions.SetConfigError(
                            'Could not copy temporary '
                            'file to startup-config.')
                except (pexpect.EOF, pexpect.TIMEOUT), e:
                    raise exceptions.SetConfigError(str(e))
Example #3
0
def Connect(hostname,
            username,
            password=None,
            port=22,
            ssh_keys=(),
            timeout=TIMEOUT_DEFAULT):
    """Makes a paramiko SSH connection to a device.

  Args:
    hostname: A string, the hostname or IP address to connect to.
    username: A string, the username to use on the connection.
    password: A string, the password to use on the connection.
    port: An int, the port number to connect to.
    ssh_keys: A tuple of strings, SSH private keys (optional; may be None).
    timeout: A float, the number of seconds before a connection times out.

  Returns:
    A paramiko.SSHClient() instance
  """

    options = SshOptions()
    hostname, port, username = options.Lookup(hostname, port, username)
    ssh_client = None

    def RaiseError(e, msg):
        """Raises an exception, disconnecting the SSH client.

    Args:
      e: An Exception.
      msg: An object, exception arguments.
    """
        raise e(msg)

    try:
        ssh_client = paramiko.SSHClient()
        # Always auto-add remote SSH host keys.
        ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        ssh_client.load_system_host_keys()
        # Connect using paramiko with a timeout parameter (requires paramiko 1.7)
        if ssh_keys:
            pkeys = []
            for key in ssh_keys:
                logging.debug(
                    'Using SSH private key for device authentication.')
                # Use a virtual temporary file to store the key.
                ssh_key_fileobj = cStringIO.StringIO()
                ssh_key_fileobj.write(key)
                ssh_key_fileobj.reset()
                try:
                    pkeys.append(paramiko.DSSKey(file_obj=ssh_key_fileobj))
                    logging.debug('Using SSH DSA key for %r', hostname)
                except (IndexError, paramiko.SSHException) as e:
                    if (isinstance(e, IndexError)
                            or 'not a valid DSA private key file' in str(e)):
                        ssh_key_fileobj.reset()
                        try:
                            logging.debug('Using SSH RSA key for %r', hostname)
                            pkeys.append(
                                paramiko.RSAKey(file_obj=ssh_key_fileobj))
                        except (IndexError, paramiko.SSHException) as e:
                            raise exceptions.AuthenticationError(str(e))
                    else:
                        raise exceptions.ConnectError('SSHException: %s' %
                                                      str(e))
        else:
            logging.debug('Using password for %r', hostname)
            pkeys = [None]
        for pkey in pkeys:
            saved_exception = None
            try:
                ssh_client.connect(hostname=hostname,
                                   port=port,
                                   username=username,
                                   password=password,
                                   pkey=pkey,
                                   timeout=timeout,
                                   allow_agent=FLAGS.use_ssh_agent,
                                   look_for_keys=False)
                break
            except (paramiko.AuthenticationException,
                    paramiko.SSHException) as e:
                saved_exception = e
        if saved_exception is not None:
            raise saved_exception  # pylint: disable=raising-bad-type
        transport = ssh_client.get_transport()
        # Sometimes we have to authenticate a second time, eg. on Force10
        # we always fail the first authentication (if we try pkey + pass,
        # the pass succeeds; but if we do pass only, we have to do it
        # twice).  connect() above will have authenticated once.
        if not transport.is_authenticated():
            if pkeys != [None]:
                for pkey in pkeys:
                    try:
                        transport.auth_publickey(username, pkey)
                        break
                    except paramiko.SSHException:
                        pass
        if not transport.is_authenticated():
            if password is not None:
                try:
                    transport.auth_password(username, password)
                except paramiko.SSHException:
                    pass
        if not transport.is_authenticated():
            msg = 'Not authenticated after two attempts on %r' % hostname
            RaiseError(exceptions.ConnectError, msg)
    except EOFError:
        msg = 'EOFError connecting to: %r' % hostname
        RaiseError(exceptions.ConnectError, msg)
    except paramiko.AuthenticationException as e:
        msg = 'Authentication error connecting to %s: %s' % (hostname, str(e))
        RaiseError(exceptions.AuthenticationError, msg)
    except paramiko.SSHException as e:
        msg = 'SSHException connecting to %s: %s' % (hostname, str(e))
        RaiseError(exceptions.ConnectError, msg)
    except socket.timeout as e:
        msg = 'Timed-out while connecting to %s: %s' % (hostname, str(e))
        RaiseError(exceptions.ConnectError, msg)
    except socket.error as e:
        msg = 'Socket error connecting to %r: %s %s' % (hostname, e.__class__,
                                                        e)
        RaiseError(exceptions.ConnectError, msg)

    return ssh_client