Exemplo n.º 1
0
    def _verify_key_value(self) -> None:
        """
        Verify target host public key, raise exception if invalid/unknown

        Args:
            N/A

        Returns:
            None

        Raises:
            ScrapliConnectionNotOpened: if session is unopened/None
            ScrapliAuthenticationFailed: if host is in known hosts but public key does not match or
                cannot glean remote server key from session.

        """
        if not self.session:
            raise ScrapliConnectionNotOpened

        known_hosts = SSHKnownHosts(self.plugin_transport_args.ssh_known_hosts_file)
        known_host_public_key = known_hosts.lookup(self._base_transport_args.host)

        remote_server_key = self.session.get_server_host_key()
        if remote_server_key is None:
            raise ScrapliAuthenticationFailed(
                f"failed gleaning remote server ssh key for host {self._base_transport_args.host}"
            )

        remote_public_key = remote_server_key.export_public_key().split()[1].decode()

        if known_host_public_key["public_key"] != remote_public_key:
            raise ScrapliAuthenticationFailed(
                f"{self._base_transport_args.host} in known_hosts but public key does not match!"
            )
Exemplo n.º 2
0
    def channel_authenticate_netconf(self, auth_password: str,
                                     auth_private_key_passphrase: str) -> None:
        """
        Handle SSH Authentication for transports that only operate "in the channel" (i.e. system)

        Args:
            auth_password: password to authenticate with
            auth_private_key_passphrase: passphrase for ssh key if necessary

        Returns:
            None

        Raises:
            ScrapliAuthenticationFailed: if password prompt seen more than twice
            ScrapliAuthenticationFailed: if passphrase prompt seen more than twice

        """
        self.logger.debug("attempting in channel netconf authentication")

        password_count = 0
        passphrase_count = 0
        authenticate_buf = b""

        with self._channel_lock():
            while True:
                buf = self.read()

                authenticate_buf += buf.lower()
                self._capabilities_buf += buf

                self._ssh_message_handler(output=authenticate_buf)

                if b"password" in authenticate_buf:
                    # clear the authentication buffer so we don't re-read the password prompt
                    authenticate_buf = b""
                    password_count += 1
                    if password_count > 2:
                        msg = "password prompt seen more than once, assuming auth failed"
                        self.logger.critical(msg)
                        raise ScrapliAuthenticationFailed(msg)
                    self.write(channel_input=auth_password, redacted=True)
                    self.send_return()

                if b"enter passphrase for key" in authenticate_buf:
                    # clear the authentication buffer so we don't re-read the passphrase prompt
                    authenticate_buf = b""
                    passphrase_count += 1
                    if passphrase_count > 2:
                        msg = "passphrase prompt seen more than once, assuming auth failed"
                        self.logger.critical(msg)
                        raise ScrapliAuthenticationFailed(msg)
                    self.write(channel_input=auth_private_key_passphrase,
                               redacted=True)
                    self.send_return()

                if self._authenticate_check_hello(buf=authenticate_buf):
                    self.logger.info(
                        "found start of server capabilities, authentication successful"
                    )
                    return
Exemplo n.º 3
0
    async def open(self) -> None:
        self._pre_open_closing_log(closing=False)

        if self.plugin_transport_args.auth_strict_key:
            self.logger.debug(
                f"Attempting to validate {self._base_transport_args.host} public key is in known "
                f"hosts"
            )
            self._verify_key()

        # we already fetched host/port/user from the user input and/or the ssh config file, so we
        # want to use those explicitly. likewise we pass config file we already found. set known
        # hosts and agent to None so we can not have an agent and deal w/ known hosts ourselves
        common_args = {
            "host": self._base_transport_args.host,
            "port": self._base_transport_args.port,
            "username": self.plugin_transport_args.auth_username,
            "known_hosts": None,
            "agent_path": None,
            "config": self.plugin_transport_args.ssh_config_file,
        }

        try:
            self.session = await asyncio.wait_for(
                connect(
                    client_keys=self.plugin_transport_args.auth_private_key,
                    password=self.plugin_transport_args.auth_password,
                    preferred_auth=(
                        "publickey",
                        "keyboard-interactive",
                        "password",
                    ),
                    **common_args,
                ),
                timeout=self._base_transport_args.timeout_socket,
            )
        except PermissionDenied as exc:
            msg = "all authentication methods failed"
            self.logger.critical(msg)
            raise ScrapliAuthenticationFailed(msg) from exc
        except asyncio.TimeoutError as exc:
            msg = "timed out opening connection to device"
            self.logger.critical(msg)
            raise ScrapliAuthenticationFailed(msg) from exc

        if not self.session:
            raise ScrapliConnectionNotOpened

        if self.plugin_transport_args.auth_strict_key:
            self.logger.debug(
                f"Attempting to validate {self._base_transport_args.host} public key is in known "
                f"hosts and is valid"
            )
            self._verify_key_value()

        self.stdin, self.stdout, _ = await self.session.open_session(
            term_type="xterm", encoding=None
        )

        self._post_open_closing_log(closing=False)
Exemplo n.º 4
0
    def _verify_key(self) -> None:
        """
        Verify target host public key, raise exception if invalid/unknown

        Args:
            N/A

        Returns:
            None

        Raises:
            ScrapliConnectionNotOpened: if session is unopened/None
            ScrapliAuthenticationFailed: if host is not in known hosts
            ScrapliAuthenticationFailed: if host is in known hosts but public key does not match

        """
        if not self.session:
            raise ScrapliConnectionNotOpened

        known_hosts = SSHKnownHosts(
            self.plugin_transport_args.ssh_known_hosts_file)

        if self._base_transport_args.host not in known_hosts.hosts.keys():
            raise ScrapliAuthenticationFailed(
                f"{self._base_transport_args.host} not in known_hosts!")

        remote_server_key = self.session.get_remote_server_key()
        remote_public_key = remote_server_key.get_base64()

        if known_hosts.hosts[self._base_transport_args.
                             host]["public_key"] != remote_public_key:
            raise ScrapliAuthenticationFailed(
                f"{self._base_transport_args.host} in known_hosts but public key does not match!"
            )
Exemplo n.º 5
0
    def open(self) -> None:
        """
        Open telnet channel

        Args:
            N/A

        Returns:
            N/A  # noqa: DAR202

        Raises:
            ScrapliAuthenticationFailed: if cant successfully authenticate

        """
        self.session_lock.acquire()
        # establish session with "socket" timeout, then reset timeout to "transport" timeout
        try:
            telnet_session = ScrapliTelnet(host=self.host,
                                           port=self.port,
                                           timeout=self.timeout_socket)
        except ConnectionError as exc:
            msg = f"Failed to open telnet session to host {self.host}"
            if "connection refused" in str(exc).lower():
                msg = f"Failed to open telnet session to host {self.host}, connection refused"
            raise ScrapliAuthenticationFailed(msg)
        telnet_session.timeout = self.timeout_transport
        LOG.debug(f"Session to host {self.host} spawned")
        self.session_lock.release()
        self._authenticate(telnet_session=telnet_session)
        if not self._telnet_isauthenticated(telnet_session=telnet_session):
            raise ScrapliAuthenticationFailed(
                f"Could not authenticate over telnet to host: {self.host}")
        LOG.debug(f"Authenticated to host {self.host} with password")
        self.session = telnet_session
Exemplo n.º 6
0
    def _verify_key(self) -> None:
        """
        Verify target host public key, raise exception if invalid/unknown

        Args:
            N/A

        Returns:
            None

        Raises:
            ScrapliConnectionNotOpened: if session is unopened/None
            ScrapliAuthenticationFailed: if public key verification fails

        """
        if not self.session:
            raise ScrapliConnectionNotOpened

        known_hosts = SSHKnownHosts(
            self.plugin_transport_args.ssh_known_hosts_file)

        if self._base_transport_args.host not in known_hosts.hosts.keys():
            raise ScrapliAuthenticationFailed(
                f"{self._base_transport_args.host} not in known_hosts!")

        remote_server_key_info = self.session.hostkey()
        encoded_remote_server_key = remote_server_key_info[0]
        raw_remote_public_key = base64.encodebytes(encoded_remote_server_key)
        remote_public_key = raw_remote_public_key.replace(b"\n", b"").decode()

        if known_hosts.hosts[self._base_transport_args.
                             host]["public_key"] != remote_public_key:
            raise ScrapliAuthenticationFailed(
                f"{self._base_transport_args.host} in known_hosts but public key does not match!"
            )
Exemplo n.º 7
0
    def _authenticate(self) -> bytes:
        """
        Private method to check initial authentication when using pty_session

        Args:
            N/A

        Returns:
            N/A  # noqa: DAR202

        Raises:
            ScrapliAuthenticationFailed: if we see a password prompt more than once, or we got an
                unhandled EOF message

        """
        self.session_lock.acquire()
        output = b""
        password_count = 0
        while True:
            try:
                new_output = self.session.read()
                output += new_output
                self.logger.debug(
                    f"Attempting to authenticate. Read: {repr(new_output)}")
            except EOFError as exc:
                self._ssh_message_handler(output=output)
                # if _ssh_message_handler didn't raise any exception, we can raise the standard --
                # did you disable strict key message/exception
                msg = (
                    f"Failed to open connection to host {self.host}. Do you need to disable "
                    "`auth_strict_key`?")
                self.logger.critical(msg)
                self.session_lock.release()
                raise ScrapliAuthenticationFailed(msg) from exc
            if b"password:"******""
                self.logger.info("Found password prompt, sending password")
                self.session.write(self.auth_password.encode())
                self.session.write(self._comms_return_char.encode())
            if password_count > 1:
                msg = (
                    "`password` seen multiple times during session establishment, "
                    "likely failed authentication")
                self.session_lock.release()
                raise ScrapliAuthenticationFailed(msg)
            if b"<hello" in output.lower():
                self.logger.info(
                    "Found start of server capabilities, authentication successful"
                )
                self._isauthenticated = True
                self.session_lock.release()
                return output
Exemplo n.º 8
0
    def open(self) -> None:
        """
        Parent method to open session, authenticate and acquire shell

        If possible it is preferable to use the `_open_pipes` method, but we can only do this IF we
        can authenticate with public key authorization (because we don't have to spawn a PTY; if no
        public key we have to spawn PTY to deal w/ authentication prompts). IF we get a private key
        provided, use pipes method, otherwise we will just deal with `_open_pty`. `_open_pty` is
        less preferable because we have to spawn a PTY and cannot as easily tell if SSH
        authentication is successful. With `_open_pipes` we can read stderr which contains the
        output from the verbose flag for SSH -- this contains a message that indicates success of
        SSH auth. In the case of `_open_pty` we have to read from the channel directly like in the
        case of telnet... so it works, but its just a bit less desirable.

        Args:
            N/A

        Returns:
            N/A  # noqa: DAR202

        Raises:
            ScrapliAuthenticationFailed: if all authentication means fail

        """
        self.session_lock.acquire()

        # If authenticating with private key prefer to use open pipes
        # _open_pipes uses subprocess Popen which is preferable to opening a pty
        # if _open_pipes fails and no password available, raise failure, otherwise try password auth
        if self.auth_private_key:
            open_pipes_result = self._open_pipes()
            if open_pipes_result:
                return
            if not self.auth_password or not self.auth_username:
                msg = (
                    f"Public key authentication to host {self.host} failed. Missing username or"
                    " password unable to attempt password authentication.")
                LOG.critical(msg)
                raise ScrapliAuthenticationFailed(msg)

        # If public key auth fails or is not configured, open a pty session
        if not self._open_pty():
            msg = f"Authentication to host {self.host} failed"
            LOG.critical(msg)
            raise ScrapliAuthenticationFailed(msg)

        if self.keepalive:
            self._session_keepalive()
Exemplo n.º 9
0
    def authenticate(self) -> None:
        """
        Parent method to try all means of authentication

        Args:
            N/A

        Returns:
            N/A  # noqa: DAR202

        Raises:
            ScrapliAuthenticationFailed: if authentication fails

        """
        if self.auth_private_key:
            self._authenticate_public_key()
            if self.isauthenticated():
                LOG.debug(f"Authenticated to host {self.host} with public key auth")
                return
            if not self.auth_password or not self.auth_username:
                msg = (
                    f"Public key authentication to host {self.host} failed. Missing username or"
                    " password unable to attempt password authentication."
                )
                LOG.critical(msg)
                raise ScrapliAuthenticationFailed(msg)
        self._authenticate_password()
        if self.isauthenticated():
            LOG.debug(f"Authenticated to host {self.host} with password")
Exemplo n.º 10
0
    def _pty_authenticate(self, pty_session: PtyProcess) -> None:
        """
        Private method to check initial authentication when using pty_session

        Args:
            pty_session: PtyProcess session object

        Returns:
            N/A  # noqa: DAR202

        Raises:
            ScrapliAuthenticationFailed: if we receive an EOFError -- this usually indicates that
                host key checking is enabled and failed.

        """
        self.session_lock.acquire()
        output = b""
        while True:
            try:
                new_output = pty_session.read()
                output += new_output
                LOG.debug(f"Attempting to authenticate. Read: {repr(new_output)}")
            except EOFError:
                msg = self._pty_authentication_eof_handler(output)
                LOG.critical(msg)
                raise ScrapliAuthenticationFailed(msg)
            if self._comms_ansi:
                output = strip_ansi(output)
            if b"password" in output.lower():
                LOG.info("Found password prompt, sending password")
                pty_session.write(self.auth_password.encode())
                pty_session.write(self._comms_return_char.encode())
                self.session_lock.release()
                break
Exemplo n.º 11
0
    def _escalate(self, escalate_priv: PrivilegeLevel) -> None:
        """
        Escalate to the next privilege level up

        Args:
            escalate_priv: privilege level to escalate to

        Returns:
            None

        Raises:
            ScrapliAuthenticationFailed: if auth escalation timeout

        """
        self._pre_escalate(escalate_priv=escalate_priv)

        if escalate_priv.escalate_auth is False:
            self.channel.send_input(channel_input=escalate_priv.escalate)
        else:
            try:
                super().send_interactive(
                    interact_events=[
                        (escalate_priv.escalate, escalate_priv.escalate_prompt,
                         False),
                        (self.auth_secondary, escalate_priv.pattern, True),
                    ],
                    interaction_complete_patterns=[escalate_priv.pattern],
                )
            except ScrapliTimeout as exc:
                raise ScrapliAuthenticationFailed(
                    f"failed escalating privilege from '{escalate_priv.previous_priv}' to "
                    f"'{escalate_priv.name}'. do you need to set an 'auth_secondary' password?"
                ) from exc
Exemplo n.º 12
0
    def _authenticate(self) -> None:
        """
        Parent method to try all means of authentication

        Args:
            N/A

        Returns:
            None

        Raises:
            ScrapliConnectionNotOpened: if session is unopened/None
            ScrapliAuthenticationFailed: if auth fails

        """
        if not self.session:
            raise ScrapliConnectionNotOpened

        if self.plugin_transport_args.auth_private_key:
            self._authenticate_public_key()
            if self.session.is_authenticated():
                return
            if (not self.plugin_transport_args.auth_password
                    or not self.plugin_transport_args.auth_username):
                msg = (
                    f"Failed to authenticate to host {self._base_transport_args.host} with private "
                    f"key `{self.plugin_transport_args.auth_private_key}`. Unable to continue "
                    "authentication, missing username, password, or both.")
                raise ScrapliAuthenticationFailed(msg)

        self._authenticate_password()
Exemplo n.º 13
0
    def _pty_authenticate(self, pty_session: PtyProcess) -> None:
        """
        Private method to check initial authentication when using pty_session

        Args:
            pty_session: PtyProcess session object

        Returns:
            N/A  # noqa: DAR202

        Raises:
            ScrapliAuthenticationFailed: if we receive an EOFError -- this usually indicates that
                host key checking is enabled and failed.

        """
        self.session_lock.acquire()
        output = b""
        while True:
            try:
                output += pty_session.read()
            except EOFError:
                msg = f"Failed to open connection to host {self.host}"
                if b"Host key verification failed" in output:
                    msg = f"Host key verification failed for host {self.host}"
                elif b"Operation timed out" in output:
                    msg = f"Timed out connecting to host {self.host}"
                raise ScrapliAuthenticationFailed(msg)
            if self._comms_ansi:
                output = strip_ansi(output)
            if b"password" in output.lower():
                LOG.debug("Found password prompt, sending password")
                pty_session.write(self.auth_password.encode())
                pty_session.write(self._comms_return_char.encode())
                self.session_lock.release()
                break
Exemplo n.º 14
0
    async def open(self) -> None:
        self._pre_open_closing_log(closing=False)

        try:
            fut = asyncio.open_connection(
                host=self._base_transport_args.host, port=self._base_transport_args.port
            )
            self.stdout, self.stdin = await asyncio.wait_for(
                fut, timeout=self._base_transport_args.timeout_socket
            )
        except ConnectionError as exc:
            msg = f"Failed to open telnet session to host {self._base_transport_args.host}"
            if "connection refused" in str(exc).lower():
                msg = (
                    f"Failed to open telnet session to host {self._base_transport_args.host}, "
                    "connection refused"
                )
            raise ScrapliConnectionError(msg) from exc
        except (OSError, socket.gaierror) as exc:
            msg = (
                f"Failed to open telnet session to host {self._base_transport_args.host} -- "
                "do you have a bad host/port?"
            )
            raise ScrapliConnectionError(msg) from exc
        except asyncio.TimeoutError as exc:
            msg = "timed out opening connection to device"
            self.logger.critical(msg)
            raise ScrapliAuthenticationFailed(msg) from exc

        await self._handle_control_chars()

        self._post_open_closing_log(closing=False)
Exemplo n.º 15
0
    def _open_pty(self) -> bool:
        """
        Private method to open session with PtyProcess

        Args:
            N/A

        Returns:
            bool: True/False session was opened and authenticated

        Raises:
            ScrapliAuthenticationFailed: if all authentication means fail

        """
        self.logger.info(
            f"Attempting to open session with the following command: {self.open_cmd}"
        )
        self.session = PtyProcess.spawn(self.open_cmd)
        self.logger.debug(f"Session to host {self.host} spawned")

        if not self.auth_bypass:
            self._authenticate()
        else:
            self.logger.info("`auth_bypass` is True, bypassing authentication")
            # if we skip auth, we'll manually set _isauthenticated to True
            self._isauthenticated = True

        if not self._isauthenticated and not self._system_isauthenticated():
            msg = f"Authentication to host {self.host} failed"
            self.logger.critical(msg)
            raise ScrapliAuthenticationFailed(msg)

        self.logger.debug(f"Authenticated to host {self.host} with password")
        return True
Exemplo n.º 16
0
    def _authenticate(self) -> None:
        """
        Parent method to try all means of authentication

        Args:
            N/A

        Returns:
            N/A  # noqa: DAR202

        Raises:
            ScrapliAuthenticationFailed: if auth fails

        """
        if self.auth_private_key:
            self._authenticate_public_key()
            if self._isauthenticated():
                LOG.debug(f"Authenticated to host {self.host} with public key auth")
                return
            if not self.auth_password or not self.auth_username:
                msg = (
                    f"Failed to authenticate to host {self.host} with private key "
                    f"`{self.auth_private_key}`. Unable to continue authentication, "
                    "missing username, password, or both."
                )
                LOG.critical(msg)
                raise ScrapliAuthenticationFailed(msg)

        self._authenticate_password()
        if self._isauthenticated():
            LOG.debug(f"Authenticated to host {self.host} with password")
            return
        self._authenticate_keyboard_interactive()
        if self._isauthenticated():
            LOG.debug(f"Authenticated to host {self.host} with keyboard interactive")
Exemplo n.º 17
0
    def _authenticate(self) -> None:
        """
        Private method to check initial authentication when using pty_session

        Args:
            N/A

        Returns:
            N/A  # noqa: DAR202

        Raises:
            ScrapliAuthenticationFailed: if we get EOF and _ssh_message_handler does not raise an
                explicit exception/message

        """
        output = b""
        prompt_pattern = get_prompt_pattern(
            prompt="", class_prompt=self._comms_prompt_pattern)
        while True:
            try:
                new_output = self.session.read()
                output += new_output
                self.logger.debug(
                    f"Attempting to authenticate. Read: {repr(new_output)}")
            except EOFError as exc:
                self._ssh_message_handler(output=output)
                # if _ssh_message_handler didn't raise any exception, we can raise the standard
                # "did you disable strict key message/exception"
                msg = (
                    f"Failed to open connection to host {self.host}. Do you need to disable "
                    "`auth_strict_key`?")
                self.logger.critical(msg)
                raise ScrapliAuthenticationFailed(msg) from exc

            # even if we dont hit EOF, we may still have hit a message we need to process such
            # as insecure key permissions
            self._ssh_message_handler(output=output)

            if self._comms_ansi or b"\x1B" in output:
                output = strip_ansi(output=output)
            if b"password" in output.lower():
                self.logger.info("Found password prompt, sending password")
                self.session.write(self.auth_password.encode())
                self.session.write(self._comms_return_char.encode())
                break
            if b"enter passphrase for key" in output.lower():
                self.logger.info(
                    "Found key passphrase prompt, sending passphrase")
                self.session.write(self.auth_private_key_passphrase.encode())
                self.session.write(self._comms_return_char.encode())
                break
            channel_match = re.search(pattern=prompt_pattern, string=output)
            if channel_match:
                self.logger.info(
                    "Found channel prompt, assuming that key based authentication has succeeded"
                )
                # set _isauthenticated to true, we've already authenticated, don't re-check
                self._isauthenticated = True
                break
Exemplo n.º 18
0
    def _ssh_message_handler(self, output: bytes) -> None:  # noqa: C901
        """
        Parse EOF messages from _pty_authenticate and create log/stack exception message

        Args:
            output: bytes output from _pty_authenticate

        Returns:
            N/A  # noqa: DAR202

        Raises:
            ScrapliAuthenticationFailed: if any errors are read in the output

        """
        msg = ""
        if b"host key verification failed" in output.lower():
            msg = "Host key verification failed"
        elif b"operation timed out" in output.lower(
        ) or b"connection timed out" in output.lower():
            msg = "Timed out connecting to host"
        elif b"no route to host" in output.lower():
            msg = "No route to host"
        elif b"no matching key exchange" in output.lower():
            msg = "No matching key exchange found for host"
            key_exchange_pattern = re.compile(
                pattern=rb"their offer: ([a-z0-9\-,]*)", flags=re.M | re.I)
            offered_key_exchanges_match = re.search(
                pattern=key_exchange_pattern, string=output)
            if offered_key_exchanges_match:
                offered_key_exchanges = offered_key_exchanges_match.group(
                    1).decode()
                msg += f", their offer: {offered_key_exchanges}"
        elif b"no matching cipher" in output.lower():
            msg = "No matching cipher found for host"
            ciphers_pattern = re.compile(
                pattern=rb"their offer: ([a-z0-9\-,]*)", flags=re.M | re.I)
            offered_ciphers_match = re.search(pattern=ciphers_pattern,
                                              string=output)
            if offered_ciphers_match:
                offered_ciphers = offered_ciphers_match.group(1).decode()
                msg += f", their offer: {offered_ciphers}"
        elif b"bad configuration" in output.lower():
            msg = "Bad SSH configuration option(s) for host"
            configuration_pattern = re.compile(
                pattern=rb"bad configuration option: ([a-z0-9\+\=,]*)",
                flags=re.M | re.I)
            configuration_issue_match = re.search(
                pattern=configuration_pattern, string=output)
            if configuration_issue_match:
                configuration_issues = configuration_issue_match.group(
                    1).decode()
                msg += f", bad option(s): {configuration_issues}"
        elif b"WARNING: UNPROTECTED PRIVATE KEY FILE!" in output:
            msg = "Permissions for private key are too open, authentication failed!"
        elif b"could not resolve hostname" in output.lower():
            msg = "Could not resolve address for host"
        if msg:
            self.logger.critical(msg)
            raise ScrapliAuthenticationFailed(msg)
Exemplo n.º 19
0
    def _authenticate(self) -> None:
        """
        Parent private method to handle telnet authentication

        Args:
            N/A

        Returns:
            N/A  # noqa: DAR202

        Raises:
            ScrapliAuthenticationFailed: if an EOFError is encountered; we in theory *did* open the
                connection, so we won't raise a ConnectionNotOpened here

        """
        output = b""

        # capture the start time of the authentication event; we also set a "return_interval" which
        # is 1/10 the timout_ops value, we will send a return character at roughly this interval if
        # there is no output on the channel. we do this because sometimes telnet needs a kick to get
        # it to prompt for auth -- particularity when connecting to terminal server/console port
        auth_start_time = datetime.now().timestamp()
        return_interval = self._timeout_ops / 10
        return_attempts = 1

        with self.session_lock:
            while True:
                try:
                    new_output = self.session.read_eager()
                    output += new_output
                    self.logger.debug(f"Attempting to authenticate. Read: {repr(new_output)}")
                except EOFError as exc:
                    # EOF means telnet connection is dead :(
                    msg = f"Failed to open connection to host {self.host}. Connection lost."
                    self.logger.critical(msg)
                    raise ScrapliAuthenticationFailed(msg) from exc

                if self.username_prompt.lower().encode() in output.lower():
                    self.logger.info("Found username prompt, sending username")
                    # if/when we see username, reset the output to empty byte string
                    output = b""
                    self.session.write(self.auth_username.encode())
                    self.session.write(self._comms_return_char.encode())
                elif self.password_prompt.lower().encode() in output.lower():
                    self.logger.info("Found password prompt, sending password")
                    self.session.write(self.auth_password.encode())
                    self.session.write(self._comms_return_char.encode())
                    break
                elif not output:
                    current_iteration_time = datetime.now().timestamp()
                    if (current_iteration_time - auth_start_time) > (
                        return_interval * return_attempts
                    ):
                        self.logger.debug(
                            "No username or password prompt found, sending return character..."
                        )
                        self.session.write(self._comms_return_char.encode())
                        return_attempts += 1
Exemplo n.º 20
0
    async def _authenticate(self) -> None:
        """
        Parent method to try all means of authentication

        Args:
            N/A

        Returns:
            N/A  # noqa: DAR202

        Raises:
            ScrapliAuthenticationFailed: if authentication fails

        """
        common_args = {
            "host": self.host,
            "port": self.port,
            "username": self.auth_username,
            "known_hosts": None,
            "agent_path": None,
        }

        if self.transport_options:
            common_args.update(**self.transport_options)

        if self.auth_private_key:
            if await self._authenticate_private_key(common_args=common_args):
                self.logger.debug(
                    f"Authenticated to host {self.host} with public key auth")
                return
            if not self.auth_password or not self.auth_username:
                msg = (
                    f"Failed to authenticate to host {self.host} with private key "
                    f"`{self.auth_private_key}`. Unable to continue authentication, "
                    "missing username, password, or both.")
                self.logger.critical(msg)
                raise ScrapliAuthenticationFailed(msg)

        if not await self._authenticate_password(common_args=common_args):
            msg = f"Authentication to host {self.host} failed"
            self.logger.critical(msg)
            self.session_lock.release()
            raise ScrapliAuthenticationFailed(msg)

        self.logger.debug(f"Authenticated to host {self.host} with password")
Exemplo n.º 21
0
    def open(self) -> None:
        """
        Parent method to open session, authenticate and acquire shell

        Args:
            N/A

        Returns:
            N/A  # noqa: DAR202

        Raises:
            exc: if socket handshake fails
            ScrapliAuthenticationFailed: if all authentication means fail

        """
        if not self.socket.socket_isalive():
            self.socket.socket_open()
        self.session_lock.acquire()
        self.session = self.lib_session()
        self.set_timeout(self.timeout_transport)
        try:
            self.session.handshake(self.socket.sock)
        except Exception as exc:
            LOG.critical(
                f"Failed to complete handshake with host {self.host}; "
                f"Exception: {exc}")
            self.session_lock.release()
            raise exc

        if self.auth_strict_key:
            LOG.debug(f"Attempting to validate {self.host} public key")
            self._verify_key()

        LOG.debug(f"Session to host {self.host} opened")
        self.authenticate()
        if not self.isauthenticated():
            msg = f"Authentication to host {self.host} failed"
            LOG.critical(msg)
            self.session_lock.release()
            raise ScrapliAuthenticationFailed(msg)
        self._open_channel()
        if self.keepalive:
            self._session_keepalive()
        self.session_lock.release()
Exemplo n.º 22
0
    def open(self) -> None:
        """
        Open telnet channel

        Args:
            N/A

        Returns:
            N/A  # noqa: DAR202

        Raises:
            ConnectionNotOpened: if connection cant be opened
            ScrapliAuthenticationFailed: if cant successfully authenticate

        """
        with self.session_lock:
            # establish session with "socket" timeout, then reset timeout to "transport" timeout
            try:
                self.session = ScrapliTelnet(
                    host=self.host, port=self.port, timeout=self.timeout_socket
                )
            except ConnectionError as exc:
                msg = f"Failed to open telnet session to host {self.host}"
                if "connection refused" in str(exc).lower():
                    msg = f"Failed to open telnet session to host {self.host}, connection refused"
                raise ConnectionNotOpened(msg) from exc
            self.session.timeout = self.timeout_transport
            self.logger.debug(f"Session to host {self.host} spawned")

        if not self.auth_bypass:
            self._authenticate()
        else:
            self.logger.info("`auth_bypass` is True, bypassing authentication")
            # if we skip auth, we'll manually set _isauthenticated to True
            self._isauthenticated = True

        if not self._isauthenticated and not self._telnet_isauthenticated():
            raise ScrapliAuthenticationFailed(
                f"Could not authenticate over telnet to host: {self.host}"
            )

        self.logger.debug(f"Authenticated to host {self.host} with password")
Exemplo n.º 23
0
    def _verify_key(self) -> None:
        """
        Verify target host public key, raise exception if invalid/unknown

        Args:
            N/A

        Returns:
            None

        Raises:
            ScrapliAuthenticationFailed: if host is not in known hosts

        """
        known_hosts = SSHKnownHosts(self.plugin_transport_args.ssh_known_hosts_file)

        if self._base_transport_args.host not in known_hosts.hosts.keys():
            raise ScrapliAuthenticationFailed(
                f"{self._base_transport_args.host} not in known_hosts!"
            )
Exemplo n.º 24
0
    def _pty_authenticate(self, pty_session: PtyProcess) -> None:
        """
        Private method to check initial authentication when using pty_session

        Args:
            pty_session: PtyProcess session object

        Returns:
            N/A  # noqa: DAR202

        Raises:
            ScrapliAuthenticationFailed: if we get EOF and _ssh_message_handler does not raise an
                explicit exception/message

        """
        self.session_lock.acquire()
        output = b""
        while True:
            try:
                new_output = pty_session.read()
                output += new_output
                self.logger.debug(
                    f"Attempting to authenticate. Read: {repr(new_output)}")
            except EOFError:
                self._ssh_message_handler(output=output)
                # if _ssh_message_handler didn't raise any exception, we can raise the standard --
                # did you disable strict key message/exception
                msg = (
                    f"Failed to open connection to host {self.host}. Do you need to disable "
                    "`auth_strict_key`?")
                self.logger.critical(msg)
                raise ScrapliAuthenticationFailed(msg)
            if self._comms_ansi:
                output = strip_ansi(output)
            if b"password" in output.lower():
                self.logger.info("Found password prompt, sending password")
                pty_session.write(self.auth_password.encode())
                pty_session.write(self._comms_return_char.encode())
                self.session_lock.release()
                break
Exemplo n.º 25
0
    async def open(self) -> None:
        """
        Open telnet channel

        Args:
            N/A

        Returns:
            N/A  # noqa: DAR202

        Raises:
            ConnectionNotOpened: if connection cant be opened
            ScrapliAuthenticationFailed: if cant successfully authenticate

        """
        try:
            output = await asyncio.wait_for(self._open(),
                                            timeout=self.timeout_socket)
        except asyncio.TimeoutError as exc:
            raise ConnectionNotOpened(
                "timed out opening connection and processing telnet control characters"
            ) from exc

        self.logger.debug(f"Session to host {self.host} spawned")

        if self.auth_bypass:
            self.logger.info("`auth_bypass` is True, bypassing authentication")
            # if we skip auth, we'll manually set _isauthenticated to True, the rest is up to the
            # user to handle, or things will just time out later... either way, not our problem :)
            self._isauthenticated = True
            return

        await self._authenticate(output=output)

        _telnet_isauthenticated = await self._telnet_isauthenticated()
        if not self._isauthenticated and not _telnet_isauthenticated:
            raise ScrapliAuthenticationFailed(
                f"Could not authenticate over telnet to host: {self.host}")
        self.logger.debug(f"Authenticated to host {self.host} with password")
Exemplo n.º 26
0
    def open(self) -> None:
        self._pre_open_closing_log(closing=False)

        if not self.socket:
            self.socket = Socket(
                host=self._base_transport_args.host,
                port=self._base_transport_args.port,
                timeout=self._base_transport_args.timeout_socket,
            )

        if not self.socket.isalive():
            self.socket.open()

        self.session = Session()
        self._set_timeout(value=self._base_transport_args.timeout_transport)

        try:
            self.session.handshake(self.socket.sock)
        except Exception as exc:
            self.logger.critical("failed to complete handshake with host")
            raise ScrapliConnectionNotOpened from exc

        if self.plugin_transport_args.auth_strict_key:
            self.logger.debug(
                f"attempting to validate {self._base_transport_args.host} public key"
            )
            self._verify_key()

        self._authenticate()

        if not self.session.userauth_authenticated():
            msg = "all authentication methods failed"
            self.logger.critical(msg)
            raise ScrapliAuthenticationFailed(msg)

        self._open_channel()

        self._post_open_closing_log(closing=False)
Exemplo n.º 27
0
    def open(self) -> None:
        """
        Parent method to open session, authenticate and acquire shell

        Args:
            N/A

        Returns:
            N/A  # noqa: DAR202

        Raises:
            Exception: if socket handshake fails
            ScrapliAuthenticationFailed: if all authentication means fail

        """
        if not self.socket.socket_isalive():
            self.socket.socket_open()

        try:
            self.session = ParamikoTransport(self.socket.sock)
            self.session.start_client()
        except Exception as exc:
            LOG.critical(f"Failed to complete handshake with host {self.host}; Exception: {exc}")
            raise exc

        if self.auth_strict_key:
            LOG.debug(f"Attempting to validate {self.host} public key")
            self._verify_key()

        self._authenticate()
        if not self._isauthenticated():
            msg = f"Authentication to host {self.host} failed"
            LOG.critical(msg)
            raise ScrapliAuthenticationFailed(msg)

        self._open_channel()
Exemplo n.º 28
0
    def _pipes_isauthenticated(self, pipes_session: "PopenBytes") -> bool:
        """
        Private method to check initial authentication when using subprocess.Popen

        Since we always run ssh with `-v` we can simply check the stderr (where verbose output goes)
        to see if `Authenticated to [our host]` is in the output.

        Args:
            pipes_session: Popen pipes session object

        Returns:
            bool: True/False session was authenticated

        Raises:
            ScrapliTimeout: if `Operation timed out` in stderr output
            ScrapliAuthenticationFailed: if private key permissions are too open

        """
        if pipes_session.stderr is None:
            raise ScrapliTimeout(f"Could not read stderr while connecting to host {self.host}")

        output = b""
        while True:
            output += pipes_session.stderr.read(65535)
            if f"Authenticated to {self.host}".encode() in output:
                self._isauthenticated = True
                return True
            if b"Operation timed out" in output:
                msg = f"Timed opening connection to host {self.host}"
                raise ScrapliTimeout(msg)
            if b"WARNING: UNPROTECTED PRIVATE KEY FILE!" in output:
                msg = (
                    f"Permissions for private key `{self.auth_private_key}` are too open, "
                    "authentication failed!"
                )
                raise ScrapliAuthenticationFailed(msg)
Exemplo n.º 29
0
    def channel_authenticate_telnet(self, auth_username: str = "", auth_password: str = "") -> None:
        """
        Handle Telnet Authentication

        Args:
            auth_username: username to use for telnet authentication
            auth_password: password to use for telnet authentication

        Returns:
            None

        Raises:
            ScrapliAuthenticationFailed: if password prompt seen more than twice
            ScrapliAuthenticationFailed: if login prompt seen more than twice

        """
        self.logger.debug("attempting in channel telnet authentication")

        username_count = 0
        password_count = 0
        authenticate_buf = b""

        (
            username_pattern,
            password_pattern,
            prompt_pattern,
            auth_start_time,
            return_interval,
        ) = self._pre_channel_authenticate_telnet()

        return_attempts = 1

        with self._channel_lock():
            while True:
                buf = self.read()

                if not buf:
                    current_iteration_time = datetime.now().timestamp()
                    if (current_iteration_time - auth_start_time) > (
                        return_interval * return_attempts
                    ):
                        self.send_return()
                        return_attempts += 1

                authenticate_buf += buf.lower()

                if re.search(
                    pattern=username_pattern,
                    string=authenticate_buf,
                ):
                    # clear the authentication buffer so we don't re-read the username prompt
                    authenticate_buf = b""
                    username_count += 1
                    if username_count > 2:
                        msg = "username/login prompt seen more than once, assuming auth failed"
                        self.logger.critical(msg)
                        raise ScrapliAuthenticationFailed(msg)
                    self.write(channel_input=auth_username)
                    self.send_return()

                if re.search(
                    pattern=password_pattern,
                    string=authenticate_buf,
                ):
                    # clear the authentication buffer so we don't re-read the password prompt
                    authenticate_buf = b""
                    password_count += 1
                    if password_count > 2:
                        msg = "password prompt seen more than once, assuming auth failed"
                        self.logger.critical(msg)
                        raise ScrapliAuthenticationFailed(msg)
                    self.write(channel_input=auth_password, redacted=True)
                    self.send_return()

                if re.search(
                    pattern=prompt_pattern,
                    string=authenticate_buf,
                ):
                    return
Exemplo n.º 30
0
    def channel_authenticate_ssh(
        self, auth_password: str, auth_private_key_passphrase: str
    ) -> None:
        """
        Handle SSH Authentication for transports that only operate "in the channel" (i.e. system)

        Args:
            auth_password: password to authenticate with
            auth_private_key_passphrase: passphrase for ssh key if necessary

        Returns:
            None

        Raises:
            ScrapliAuthenticationFailed: if password prompt seen more than twice
            ScrapliAuthenticationFailed: if passphrase prompt seen more than twice

        """
        self.logger.debug("attempting in channel ssh authentication")

        password_count = 0
        passphrase_count = 0
        authenticate_buf = b""

        (
            password_pattern,
            passphrase_pattern,
            prompt_pattern,
        ) = self._pre_channel_authenticate_ssh()

        with self._channel_lock():
            while True:
                buf = self.read()
                authenticate_buf += buf.lower()

                self._ssh_message_handler(output=authenticate_buf)

                if re.search(
                    pattern=password_pattern,
                    string=authenticate_buf,
                ):
                    # clear the authentication buffer so we don't re-read the password prompt
                    authenticate_buf = b""
                    password_count += 1
                    if password_count > 2:
                        msg = "password prompt seen more than once, assuming auth failed"
                        self.logger.critical(msg)
                        raise ScrapliAuthenticationFailed(msg)
                    self.write(channel_input=auth_password, redacted=True)
                    self.send_return()

                if re.search(
                    pattern=passphrase_pattern,
                    string=authenticate_buf,
                ):
                    # clear the authentication buffer so we don't re-read the passphrase prompt
                    authenticate_buf = b""
                    passphrase_count += 1
                    if passphrase_count > 2:
                        msg = "passphrase prompt seen more than once, assuming auth failed"
                        self.logger.critical(msg)
                        raise ScrapliAuthenticationFailed(msg)
                    self.write(channel_input=auth_private_key_passphrase, redacted=True)
                    self.send_return()

                if re.search(
                    pattern=prompt_pattern,
                    string=authenticate_buf,
                ):
                    return