diff options
author | Perry Randall <perryjrandall@gmail.com> | 2014-12-24 07:54:06 -0800 |
---|---|---|
committer | Perry Randall <perryjrandall@gmail.com> | 2014-12-29 15:50:09 -0800 |
commit | dae916f7bd6723cee95891778baff51ef45532ee (patch) | |
tree | dee171bf4d9cefad5234138109d585a46aac04c7 | |
parent | 424ba615c2a94d3b059e7f24db1a1093a92d8d22 (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.py | 25 | ||||
-rw-r--r-- | paramiko/transport.py | 22 |
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. |