'Multi-factor authentication (password and key) with Paramiko
I've got the following code:
import paramiko
policy = paramiko.client.WarningPolicy()
client = paramiko.client.SSHClient()
client.set_missing_host_key_policy(policy)
username = '...'
password = '...'
file_path = '...'
pkey = paramiko.RSAKey.from_private_key_file(file_path)
client.connect('...', username=username, password=password, pkey=key)
sftp = client.open_sftp()
From the docs, it seems like it should work. Everything works successfully, but when the code hits client.open_sftp it bombs with a SSHException: Unable to open channel. and the transport (from client.get_transport) is active but not authenticated. I'm also having trouble enabling debug logging for this (I'm trying logging.getLogger('paramiko').setLevel(logging.DEBUG) without success.)
Any ideas on where I can start to debug this very vague error message?
Solution 1:[1]
Sorry for the late response but this problem was really hard to find any information on so i wanted to post a solution for anyone else stuck on this issue.
After pulling my hair out trying to solve this I found a solution thanks to some code posted by Doug Ellwanger and Daniel Brownridge. The problem seems to be caused by the way the multi-factor authentication is handled using more of an interactive style.
import paramiko
import threading
...
username = '...'
password = '...'
file_path = '...'
pkey = paramiko.RSAKey.from_private_key_file(file_path)
sftpClient = multifactor_auth('...', 22, username, pkey, password)
...
def multifactor_auth_sftp_client(host, port, username, key, password):
#Create an SSH transport configured to the host
transport = paramiko.Transport((host, port))
#Negotiate an SSH2 session
transport.connect()
#Attempt authenticating using a private key
transport.auth_publickey(username, key)
#Create an event for password auth
password_auth_event = threading.Event()
#Create password auth handler from transport
password_auth_handler = paramiko.auth_handler.AuthHandler(transport)
#Set transport auth_handler to password handler
transport.auth_handler = password_auth_handler
#Aquire lock on transport
transport.lock.acquire()
#Register the password auth event with handler
password_auth_handler.auth_event = password_auth_event
#Set the auth handler method to 'password'
password_auth_handler.auth_method = 'password'
#Set auth handler username
password_auth_handler.username = username
#Set auth handler password
password_auth_handler.password = password
#Create an SSH user auth message
userauth_message = paramiko.message.Message()
userauth_message.add_string('ssh-userauth')
userauth_message.rewind()
#Make the password auth attempt
password_auth_handler._parse_service_accept(userauth_message)
#Release lock on transport
transport.lock.release()
#Wait for password auth response
password_auth_handler.wait_for_response(password_auth_event)
#Create an open SFTP client channel
return transport.open_sftp_client()
I hope this helps, it worked for my project.
Solution 2:[2]
In your script you declare
pkey = paramiko.RSAKey.from_private_key_file(file_path)
and then instead of pkey, you have pkey = key.
Not sure what key is coming from but that might be an issue.
Solution 3:[3]
The Paramiko high-level API, SSHClient can handle common two-factor authentication on its own. For example for key and password authentication, use:
ssh = paramiko.SSHClient()
ssh.connect(
"example.com", username="username", password="password",
key_filename="/path/to/key")
So the complicated code in the answer by @osekmedia is usually not needed.
I know of only two scenarios, where it can "help":
SSHClientby default verifies the host key. You may mistake failure to verify the host key with failure of the two-factor authentication. They are not related. It's just that the low-levelTransportAPI, that the @osekmedia's code uses, does not verify the host key, what avoids your actual problem. But that's a security flaw. For a correct solution, see Paramiko "Unknown Server".You might think that you are using password authentication, while you actually use keyboard-interactive authentication. Normally Paramiko can handle keyboard-interactive authentication, even if you mistakenly ask for password authentication. But with some obscure servers, this does not work, see Password authentication in Python Paramiko fails, but same credentials work in SSH/SFTP client. In such case, the following code should do:
username = "username" transport = paramiko.Transport('example.com') transport.connect(username=username) key = paramiko.RSAKey.from_private_key_file("/path/to/key") transport.auth_publickey(username, key) def handler(title, instructions, fields): if len(fields) > 1: raise SSHException("Expecting one field only.") return ["password"] transport.auth_interactive(username, handler)Note that the above code uses
Transport, so it by default bypasses host key verification. Usehostkeyargument of theTransport.connectto correct that.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | eyllanesc |
| Solution 3 |
