summaryrefslogtreecommitdiffhomepage
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)
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.