summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPerry Randall <perryjrandall@gmail.com>2014-12-24 07:54:06 -0800
committerPerry Randall <perryjrandall@gmail.com>2014-12-29 15:50:09 -0800
commitdae916f7bd6723cee95891778baff51ef45532ee (patch)
treedee171bf4d9cefad5234138109d585a46aac04c7
parent424ba615c2a94d3b059e7f24db1a1093a92d8d22 (diff)
downloadparamiko-dae916f7bd6723cee95891778baff51ef45532ee.tar.gz
Add more flexible support for two factor authentication.
Allow paramiko to partially authenticate and continue by echo'ing the prompt on the remote end and responding.
-rw-r--r--paramiko/client.py25
-rw-r--r--paramiko/transport.py22
2 files changed, 37 insertions, 10 deletions
diff --git a/paramiko/client.py b/paramiko/client.py
index 393e3e09..1ccf0456 100644
--- a/paramiko/client.py
+++ b/paramiko/client.py
@@ -404,7 +404,8 @@ class SSHClient (ClosingContextManager):
"""
saved_exception = None
two_factor = False
- allowed_types = []
+ allowed_types = set()
+ two_factor_types = set(['keyboard-interactive','password'])
# If GSS-API support and GSS-PI Key Exchange was performed, we attempt
# authentication with gssapi-keyex.
@@ -430,8 +431,8 @@ class SSHClient (ClosingContextManager):
if pkey is not None:
try:
self._log(DEBUG, 'Trying SSH key %s' % hexlify(pkey.get_fingerprint()))
- allowed_types = self._transport.auth_publickey(username, pkey)
- two_factor = (allowed_types == ['password'])
+ allowed_types = set(self._transport.auth_publickey(username, pkey))
+ two_factor = (allowed_types & two_factor_types)
if not two_factor:
return
except SSHException as e:
@@ -444,7 +445,7 @@ class SSHClient (ClosingContextManager):
key = pkey_class.from_private_key_file(key_filename, password)
self._log(DEBUG, 'Trying key %s from %s' % (hexlify(key.get_fingerprint()), key_filename))
self._transport.auth_publickey(username, key)
- two_factor = (allowed_types == ['password'])
+ two_factor = (allowed_types & two_factor_types)
if not two_factor:
return
break
@@ -458,9 +459,9 @@ class SSHClient (ClosingContextManager):
for key in self._agent.get_keys():
try:
self._log(DEBUG, 'Trying SSH agent key %s' % hexlify(key.get_fingerprint()))
- # for 2-factor auth a successfully auth'd key will result in ['password']
- allowed_types = self._transport.auth_publickey(username, key)
- two_factor = (allowed_types == ['password'])
+ # for 2-factor auth a successfully auth'd key password will return an allowed 2fac auth method
+ allowed_types = set(self._transport.auth_publickey(username, key))
+ two_factor = (allowed_types & two_factor_types)
if not two_factor:
return
break
@@ -497,8 +498,8 @@ class SSHClient (ClosingContextManager):
key = pkey_class.from_private_key_file(filename, password)
self._log(DEBUG, 'Trying discovered key %s in %s' % (hexlify(key.get_fingerprint()), filename))
# for 2-factor auth a successfully auth'd key will result in ['password']
- allowed_types = self._transport.auth_publickey(username, key)
- two_factor = (allowed_types == ['password'])
+ allowed_types = set(self._transport.auth_publickey(username, key))
+ two_factor = (allowed_types & two_factor_types)
if not two_factor:
return
break
@@ -512,7 +513,11 @@ class SSHClient (ClosingContextManager):
except SSHException as e:
saved_exception = e
elif two_factor:
- raise SSHException('Two-factor authentication requires a password')
+ try:
+ self._transport.auth_interactive_dumb(username)
+ return
+ except SSHException as e:
+ saved_exception = e
# if we got an auth-failed exception earlier, re-raise it
if saved_exception is not None:
diff --git a/paramiko/transport.py b/paramiko/transport.py
index 36da3043..4fa36191 100644
--- a/paramiko/transport.py
+++ b/paramiko/transport.py
@@ -20,6 +20,7 @@
Core protocol implementation
"""
+from __future__ import print_function
import os
import socket
import sys
@@ -1284,6 +1285,27 @@ class Transport (threading.Thread, ClosingContextManager):
self.auth_handler.auth_interactive(username, handler, my_event, submethods)
return self.auth_handler.wait_for_response(my_event)
+ def auth_interactive_dumb(self, username, handler=None, submethods=''):
+ """
+ Autenticate to the server interactively but dumber.
+ Just print the prompt and / or instructions to stdout and send back
+ the response. This is good for situations where partial auth is
+ achieved by key and then the user has to enter a 2fac token.
+ """
+
+ if not handler:
+ def handler(title, instructions, prompt_list):
+ answers = []
+ if title:
+ print(title.strip())
+ if instructions:
+ print(instructions.strip())
+ for prompt,show_input in prompt_list:
+ print(prompt.strip(),end=' ')
+ answers.append(raw_input())
+ return answers
+ return self.auth_interactive(username, handler, submethods)
+
def auth_gssapi_with_mic(self, username, gss_host, gss_deleg_creds):
"""
Authenticate to the Server using GSS-API / SSPI.