question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Connection failure if the remote requires Two-Factor Authentication (2FA)

See original GitHub issue

Hi @ronf, thanks for this amazing project! I get Permission denied when trying to use AsyncSSH to connect to a remote which requires Two-Factor Authentication (2FA).

Test Code

import logging
import asyncssh
import asyncio as aio

async def ssh_test():
    async with asyncssh.connect("c1f81187-cc18-451e-a611-34b82c4cd429", known_hosts=None):
        pass

logging.basicConfig()
asyncssh.set_log_level('DEBUG')
asyncssh.set_debug_level(2)

if __name__ == "__main__":
    print(aio.run(asyncssh.get_server_auth_methods("c1f81187-cc18-451e-a611-34b82c4cd429")))
    aio.run(ssh_test())

Log

DEBUG:asyncssh:Reading config from "/private/home/xinyuanz/.ssh/config"
DEBUG:asyncssh:Reading config from "/private/home/xinyuanz/.ssh/fair_ssh/includes"
DEBUG:asyncssh:Reading config from "/private/home/xinyuanz/.ssh/fair_ssh/xinyuanz/config"
INFO:asyncssh:Fetching server auth methods from nlb-34b82c4cd429-8c30cef286658237.elb.us-east-1.amazonaws.com, port 22
INFO:asyncssh:[conn=0] Connected to SSH server at nlb-34b82c4cd429-8c30cef286658237.elb.us-east-1.amazonaws.com, port 22
INFO:asyncssh:[conn=0]   Local address: 100.96.183.110, port 36196
INFO:asyncssh:[conn=0]   Peer address: 34.224.194.26, port 22
DEBUG:asyncssh:[conn=0] Sending version SSH-2.0-AsyncSSH_2.12.0
DEBUG:asyncssh:[conn=0] Received version SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.5
DEBUG:asyncssh:[conn=0] Requesting key exchange
DEBUG:asyncssh:[conn=0]   Key exchange algs: curve25519-sha256,curve25519-sha256@libssh.org,curve448-sha512,ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256,ecdh-sha2-1.3.132.0.10,diffie-hellman-group-exchange-sha256,diffie-hellman-group14-sha256,diffie-hellman-group15-sha512,diffie-hellman-group16-sha512,diffie-hellman-group17-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha256@ssh.com,diffie-hellman-group14-sha1,rsa2048-sha256,ext-info-c
DEBUG:asyncssh:[conn=0]   Host key algs: sk-ssh-ed25519-cert-v01@openssh.com,sk-ecdsa-sha2-nistp256-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,ssh-ed448-cert-v01@openssh.com,ecdsa-sha2-nistp521-cert-v01@openssh.com,ecdsa-sha2-nistp384-cert-v01@openssh.com,ecdsa-sha2-nistp256-cert-v01@openssh.com,ecdsa-sha2-1.3.132.0.10-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,rsa-sha2-512-cert-v01@openssh.com,ssh-rsa-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,sk-ecdsa-sha2-nistp256@openssh.com,ssh-ed25519,ssh-ed448,ecdsa-sha2-nistp521,ecdsa-sha2-nistp384,ecdsa-sha2-nistp256,ecdsa-sha2-1.3.132.0.10,rsa-sha2-256,rsa-sha2-512,ssh-rsa-sha224@ssh.com,ssh-rsa-sha256@ssh.com,ssh-rsa-sha384@ssh.com,ssh-rsa-sha512@ssh.com,ssh-rsa
DEBUG:asyncssh:[conn=0]   Encryption algs: chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr
DEBUG:asyncssh:[conn=0]   MAC algs: umac-64-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha1-etm@openssh.com,umac-64@openssh.com,umac-128@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-sha1,hmac-sha256-2@ssh.com,hmac-sha224@ssh.com,hmac-sha256@ssh.com,hmac-sha384@ssh.com,hmac-sha512@ssh.com
DEBUG:asyncssh:[conn=0]   Compression algs: zlib@openssh.com,none
DEBUG:asyncssh:[conn=0] Received key exchange request
DEBUG:asyncssh:[conn=0]   Key exchange algs: curve25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha256
DEBUG:asyncssh:[conn=0]   Host key algs: rsa-sha2-512,rsa-sha2-256,ssh-rsa,ecdsa-sha2-nistp256,ssh-ed25519
DEBUG:asyncssh:[conn=0]   Client to server:
DEBUG:asyncssh:[conn=0]     Encryption algs: aes128-cbc,aes192-cbc,aes256-cbc,aes128-ctr,aes192-ctr,aes256-ctr,aes128-gcm@openssh.com,aes256-gcm@openssh.com
DEBUG:asyncssh:[conn=0]     MAC algs: hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512,hmac-sha2-256
DEBUG:asyncssh:[conn=0]     Compression algs: none,zlib@openssh.com
DEBUG:asyncssh:[conn=0]   Server to client:
DEBUG:asyncssh:[conn=0]     Encryption algs: aes128-cbc,aes192-cbc,aes256-cbc,aes128-ctr,aes192-ctr,aes256-ctr,aes128-gcm@openssh.com,aes256-gcm@openssh.com
DEBUG:asyncssh:[conn=0]     MAC algs: hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512,hmac-sha2-256
DEBUG:asyncssh:[conn=0]     Compression algs: none,zlib@openssh.com
DEBUG:asyncssh:[conn=0] Beginning key exchange
DEBUG:asyncssh:[conn=0]   Key exchange alg: curve25519-sha256
DEBUG:asyncssh:[conn=0]   Client to server:
DEBUG:asyncssh:[conn=0]     Encryption alg: aes256-gcm@openssh.com
DEBUG:asyncssh:[conn=0]     MAC alg: aes256-gcm@openssh.com
DEBUG:asyncssh:[conn=0]     Compression alg: zlib@openssh.com
DEBUG:asyncssh:[conn=0]   Server to client:
DEBUG:asyncssh:[conn=0]     Encryption alg: aes256-gcm@openssh.com
DEBUG:asyncssh:[conn=0]     MAC alg: aes256-gcm@openssh.com
DEBUG:asyncssh:[conn=0]     Compression alg: zlib@openssh.com
DEBUG:asyncssh:[conn=0] Requesting service ssh-userauth
DEBUG:asyncssh:[conn=0] Completed key exchange
DEBUG:asyncssh:[conn=0] Received extension info
DEBUG:asyncssh:[conn=0]   server-sig-algs: ssh-ed25519,sk-ssh-ed25519@openssh.com,ssh-rsa,rsa-sha2-256,rsa-sha2-512,ssh-dss,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,sk-ecdsa-sha2-nistp256@openssh.com
DEBUG:asyncssh:[conn=0] Request for service ssh-userauth accepted
INFO:asyncssh:[conn=0] Beginning auth for user xinyuanz
DEBUG:asyncssh:[conn=0] Remaining auth methods: publickey
INFO:asyncssh:[conn=0] Aborting connection
INFO:asyncssh:[conn=0] Connection closed
['publickey']
DEBUG:asyncssh:Reading config from "/private/home/xinyuanz/.ssh/config"
DEBUG:asyncssh:Reading config from "/private/home/xinyuanz/.ssh/fair_ssh/includes"
DEBUG:asyncssh:Reading config from "/private/home/xinyuanz/.ssh/fair_ssh/xinyuanz/config"
INFO:asyncssh:Opening SSH connection to nlb-34b82c4cd429-8c30cef286658237.elb.us-east-1.amazonaws.com, port 22
INFO:asyncssh:[conn=1] Connected to SSH server at nlb-34b82c4cd429-8c30cef286658237.elb.us-east-1.amazonaws.com, port 22
INFO:asyncssh:[conn=1]   Local address: 100.96.183.110, port 36198
INFO:asyncssh:[conn=1]   Peer address: 34.224.194.26, port 22
DEBUG:asyncssh:[conn=1] Sending version SSH-2.0-AsyncSSH_2.12.0
DEBUG:asyncssh:[conn=1] Received version SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.5
DEBUG:asyncssh:[conn=1] Requesting key exchange
DEBUG:asyncssh:[conn=1]   Key exchange algs: curve25519-sha256,curve25519-sha256@libssh.org,curve448-sha512,ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256,ecdh-sha2-1.3.132.0.10,diffie-hellman-group-exchange-sha256,diffie-hellman-group14-sha256,diffie-hellman-group15-sha512,diffie-hellman-group16-sha512,diffie-hellman-group17-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha256@ssh.com,diffie-hellman-group14-sha1,rsa2048-sha256,ext-info-c
DEBUG:asyncssh:[conn=1]   Host key algs: sk-ssh-ed25519-cert-v01@openssh.com,sk-ecdsa-sha2-nistp256-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,ssh-ed448-cert-v01@openssh.com,ecdsa-sha2-nistp521-cert-v01@openssh.com,ecdsa-sha2-nistp384-cert-v01@openssh.com,ecdsa-sha2-nistp256-cert-v01@openssh.com,ecdsa-sha2-1.3.132.0.10-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,rsa-sha2-512-cert-v01@openssh.com,ssh-rsa-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,sk-ecdsa-sha2-nistp256@openssh.com,ssh-ed25519,ssh-ed448,ecdsa-sha2-nistp521,ecdsa-sha2-nistp384,ecdsa-sha2-nistp256,ecdsa-sha2-1.3.132.0.10,rsa-sha2-256,rsa-sha2-512,ssh-rsa-sha224@ssh.com,ssh-rsa-sha256@ssh.com,ssh-rsa-sha384@ssh.com,ssh-rsa-sha512@ssh.com,ssh-rsa
DEBUG:asyncssh:[conn=1]   Encryption algs: chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr
DEBUG:asyncssh:[conn=1]   MAC algs: umac-64-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha1-etm@openssh.com,umac-64@openssh.com,umac-128@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-sha1,hmac-sha256-2@ssh.com,hmac-sha224@ssh.com,hmac-sha256@ssh.com,hmac-sha384@ssh.com,hmac-sha512@ssh.com
DEBUG:asyncssh:[conn=1]   Compression algs: zlib@openssh.com,none
DEBUG:asyncssh:[conn=1] Received key exchange request
DEBUG:asyncssh:[conn=1]   Key exchange algs: curve25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha256
DEBUG:asyncssh:[conn=1]   Host key algs: rsa-sha2-512,rsa-sha2-256,ssh-rsa,ecdsa-sha2-nistp256,ssh-ed25519
DEBUG:asyncssh:[conn=1]   Client to server:
DEBUG:asyncssh:[conn=1]     Encryption algs: aes128-cbc,aes192-cbc,aes256-cbc,aes128-ctr,aes192-ctr,aes256-ctr,aes128-gcm@openssh.com,aes256-gcm@openssh.com
DEBUG:asyncssh:[conn=1]     MAC algs: hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512,hmac-sha2-256
DEBUG:asyncssh:[conn=1]     Compression algs: none,zlib@openssh.com
DEBUG:asyncssh:[conn=1]   Server to client:
DEBUG:asyncssh:[conn=1]     Encryption algs: aes128-cbc,aes192-cbc,aes256-cbc,aes128-ctr,aes192-ctr,aes256-ctr,aes128-gcm@openssh.com,aes256-gcm@openssh.com
DEBUG:asyncssh:[conn=1]     MAC algs: hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512,hmac-sha2-256
DEBUG:asyncssh:[conn=1]     Compression algs: none,zlib@openssh.com
DEBUG:asyncssh:[conn=1] Beginning key exchange
DEBUG:asyncssh:[conn=1]   Key exchange alg: curve25519-sha256
DEBUG:asyncssh:[conn=1]   Client to server:
DEBUG:asyncssh:[conn=1]     Encryption alg: aes256-gcm@openssh.com
DEBUG:asyncssh:[conn=1]     MAC alg: aes256-gcm@openssh.com
DEBUG:asyncssh:[conn=1]     Compression alg: zlib@openssh.com
DEBUG:asyncssh:[conn=1]   Server to client:
DEBUG:asyncssh:[conn=1]     Encryption alg: aes256-gcm@openssh.com
DEBUG:asyncssh:[conn=1]     MAC alg: aes256-gcm@openssh.com
DEBUG:asyncssh:[conn=1]     Compression alg: zlib@openssh.com
DEBUG:asyncssh:[conn=1] Requesting service ssh-userauth
DEBUG:asyncssh:[conn=1] Completed key exchange
DEBUG:asyncssh:[conn=1] Received extension info
DEBUG:asyncssh:[conn=1]   server-sig-algs: ssh-ed25519,sk-ssh-ed25519@openssh.com,ssh-rsa,rsa-sha2-256,rsa-sha2-512,ssh-dss,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,sk-ecdsa-sha2-nistp256@openssh.com
DEBUG:asyncssh:[conn=1] Request for service ssh-userauth accepted
INFO:asyncssh:[conn=1] Beginning auth for user xinyuanz
DEBUG:asyncssh:[conn=1] Remaining auth methods: publickey
DEBUG:asyncssh:[conn=1] Preferred auth methods: gssapi-keyex,gssapi-with-mic,hostbased,publickey,keyboard-interactive,password
DEBUG:asyncssh:[conn=1] Trying public key auth with rsa-sha2-256 key
DEBUG:asyncssh:[conn=1] Signing request with rsa-sha2-256 key
DEBUG:asyncssh:[conn=1] Remaining auth methods: keyboard-interactive
DEBUG:asyncssh:[conn=1] Preferred auth methods: gssapi-keyex,gssapi-with-mic,hostbased,publickey,keyboard-interactive,password
INFO:asyncssh:[conn=1] Auth failed for user xinyuanz
INFO:asyncssh:[conn=1] Connection failure: Permission denied
INFO:asyncssh:[conn=1] Aborting connection
Traceback (most recent call last):
  File "/private/home/xinyuanz/OneDevEx/test1.py", line 15, in <module>
    aio.run(ssh_test())
  File "/private/home/xinyuanz/miniconda3/envs/my-dev-env/lib/python3.10/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "/private/home/xinyuanz/miniconda3/envs/my-dev-env/lib/python3.10/asyncio/base_events.py", line 646, in run_until_complete
    return future.result()
  File "/private/home/xinyuanz/OneDevEx/test1.py", line 6, in ssh_test
    async with asyncssh.connect("c1f81187-cc18-451e-a611-34b82c4cd429", known_hosts=None):
  File "/private/home/xinyuanz/miniconda3/envs/my-dev-env/lib/python3.10/site-packages/asyncssh/misc.py", line 274, in __aenter__
    self._coro_result = await self._coro
  File "/private/home/xinyuanz/miniconda3/envs/my-dev-env/lib/python3.10/site-packages/asyncssh/connection.py", line 7834, in connect
    return await asyncio.wait_for(
  File "/private/home/xinyuanz/miniconda3/envs/my-dev-env/lib/python3.10/asyncio/tasks.py", line 408, in wait_for
    return await fut
  File "/private/home/xinyuanz/miniconda3/envs/my-dev-env/lib/python3.10/site-packages/asyncssh/connection.py", line 447, in _connect
    await options.waiter
asyncssh.misc.PermissionDenied: Permission denied

I expected a prompt asking for the authentication token (just like using the command line tool), but got denied directly. Please help! Thanks in advance!

Issue Analytics

  • State:open
  • Created a year ago
  • Comments:5 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
ronfcommented, Sep 17, 2022

AsyncSSH isn’t intended to be an interactive SSH client, so it’s never going to prompt you for anything. In particular, if you want to use password or keyboard-interactive auth, you will need to prompt the user yourself for the password and 2FA token. You can do this by implementing the kbdint_auth_requested() and kbdint_challenge_received() methods in a subclass of SSHClient. Here’s more info on these methods:

    def kbdint_auth_requested(self) -> MaybeAwait[Optional[str]]:
        """Keyboard-interactive authentication has been requested

           This method should return a string containing a comma-separated
           list of submethods that the server should use for
           keyboard-interactive authentication. An empty string can be
           returned to let the server pick the type of keyboard-interactive
           authentication to perform. If keyboard-interactive authentication
           is not supported, `None` should be returned.

           By default, keyboard-interactive authentication is supported
           if a password was provided when the :class:`SSHClient` was
           created and it hasn't been sent yet. If the challenge is not
           a password challenge, this authentication will fail. This
           method and the :meth:`kbdint_challenge_received` method can be
           overridden if other forms of challenge should be supported.

           If blocking operations need to be performed to determine the
           submethods to request, this method may be defined as a
           coroutine.

           :returns: A string containing the submethods the server should
                     use for authentication or `None` to move on to
                     another authentication method

        """

    def kbdint_challenge_received(self, name: str, instructions: str,
                                  lang: str, prompts: KbdIntPrompts) -> \
            MaybeAwait[Optional[KbdIntResponse]]:
        """A keyboard-interactive auth challenge has been received

           This method is called when the server sends a keyboard-interactive
           authentication challenge.

           The return value should be a list of strings of the same length
           as the number of prompts provided if the challenge can be
           answered, or `None` to indicate that some other form of
           authentication should be attempted.

           If blocking operations need to be performed to determine the
           responses to authenticate with, this method may be defined
           as a coroutine.

           By default, this method will look for a challenge consisting
           of a single 'Password:' prompt, and call the method
           :meth:`password_auth_requested` to provide the response.
           It will also ignore challenges with no prompts (generally used
           to provide instructions). Any other form of challenge will
           cause this method to return `None` to move on to another
           authentication method.

           :param name:
               The name of the challenge
           :param instructions:
               Instructions to the user about how to respond to the challenge
           :param lang:
               The language the challenge is in
           :param prompts:
               The challenges the user should respond to and whether or
               not the responses should be echoed when they are entered
           :type name: `str`
           :type instructions: `str`
           :type lang: `str`
           :type prompts: `list` of tuples of `str` and `bool`

           :returns: List of string responses to the challenge or `None`
                     to move on to another authentication method

        """

You’ll probably want kbdint_auth_requested() to return an empty string, to let the server pick the specific type of keyboard-interactive auth to use. Then, when the server challenges you, your kbdint_challenge_received() method should be called with the name, instructions, lang, and prompts arguments shown here. You’d then prompt the user using the values in the prompts argument and return a list of strings matching the length of prompts with your answers.

For your 2FA case, I’m not sure if you’ll get a single challenge with two prompts or of it will do that as two different rounds of authentication. I don’t have an SSH setup here that does 2FA auth.

Also, keep in mind that you’ll want to keep your user prompting as async-friendly, if you don’t want the whole event loop to stop running when prompting the user. That means if you want to use something like getpass(), you’ll probably need to run that in an executor. See the ainput() example at https://gist.github.com/delivrance/675a4295ce7dc70f0ce0b164fcdbd798?permalink_comment_id=3590322#gistcomment-3590322 for an example of what this might look like.

0reactions
ronfcommented, Oct 1, 2022

OpenSSH is a standalone executable, so it has total control over whether it exits or stays running in the background after the session it was started for closes. This isn’t the case with AsyncSSH – it is a library, and it relies on the application using it to provide the Python interpreter and asyncio event loop. If that application code decides to exit, AsyncSSH really has no say in the matter, and all of the connections it was managing will end up getting closed.

It might be possible to do something using atexit(), but I don’t think that would allow AsyncSSH to prevent the process from exiting. At best, it would have to do something like fork() (which is only available on UNIX systems) and preserve all the file descriptors associated with AsyncSSH while closing everything else. That’s still dangerous, though, as there’s no way to know what other resources the application may have created that aren’t cleaned up prior to the fork. In fact, the application may never attempt to clean up these resources, counting on exiting the process to do so.

I’m just not seeing a good way to work around these issues. Even I were to create a standalone agent which AsyncSSH had total control over for this purpose, there would be the issue of copying the connection state from the application process to this agent process. Since AsyncSSH is a library, that has other challenges, like the fact that the application may have subclassed some of the AsyncSSH classes, and there’d be no way to get that custom code running over in the other interpreter. There’d be a lot of other state which would have to be copied over as well for the agent to be able to continue to read/write data on the open connections. AsyncSSH has no mechanism for anything like that, and doesn’t need any such code today, as connections are never expected to used outside of a single Python interpreter and asyncio event loop.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Two-Factor Authentication for connections
This article provides a step-by-step guide to activating Two-factor authentication for connections (also known as TFA for connections).
Read more >
2FA (Two-factor Authentication) - remote.it
With 2FA enabled, you'll need to provide an authentication code when accessing remote.it through your browser, desktop and mobile apps. If you  ......
Read more >
Multi-Factor Authentication (MFA) For Remote Access
Multi -factor authentication is a security system that requires two or more methods of authentication from different categories that verify a ...
Read more >
What is Two-Factor Authentication (2FA) and How Does It Work?
Two -factor authentication adds an additional layer of security to the authentication process by making it harder for attackers to gain access to...
Read more >
Limit number of reconnections to avoid spamming 2fa requests
The server I am connecting to via the remote SSH extension uses SSH ... if the two-factor authentication requires the code to be...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found