使用 Paramiko 在 Python 中使用 2FA 进行 SSH [英] SSH with 2FA in Python Using Paramiko
问题描述
我正在尝试编写一个 Python 3 脚本,该脚本将使用 paramiko 模块通过 ssh 连接到远程服务器并运行命令.
I'm trying to write a Python 3 script that will connect to a remote server via ssh and run a command, using the paramiko module.
远程服务器使用 Duo 2 factor 身份验证,并在使用 ssh 连接时提示您选择身份验证模式:
The remote server uses Duo 2 factor authentication and prompts you to select an authentication mode when connecting using ssh:
$ ssh myuser@remoteserver.com
Duo two-factor login for myuser
Enter a passcode or select one of the following options:
1. Duo Push to +XXX XX-XXX-1111
2. Phone call to +XXX XX-XXX-1111
3. SMS passcodes to +XXX XX-XXX-1111
Passcode or option (1-3): 1
Success. Logging you in...
当我在终端中使用 ssh 时,我只需按 1,然后按 Enter,将推送到我的手机,在那里我批准连接,然后我就登录了.
When I use ssh in the terminal, I just press 1 and then Enter, get the push to my phone where I aprove the connection, and then I'm logged in.
不幸的是,我无法在 Python 中做到这一点.这是我尝试使用的代码:
I have not been able to do that in Python unfortunately. Here's the code I tried using:
import paramiko
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect('remoteserver.com', port=22, username='myuser', password='mypassword')
stdin, stdout, stderr = ssh.exec_command('ls -l')
output = stdout.readlines()
print(output)
如果我在没有 2FA 的远程服务器上尝试相同的代码,它会按预期工作,但使用此服务器,我会收到身份验证错误:
If I try the same code on a remote server without 2FA, it works as expected but with this server, I get an authenticatino error:
paramiko.ssh_exception.AuthenticationException: Authentication failed.
任何帮助将不胜感激.
推荐答案
刚刚在我的一个项目中自己解决了这个问题,我将留下我使用的允许这项工作的代码.
Having just finally solved this problem myself in one of my projects going to leave the code I used which allowed for this work.
主要内容是 paramiko 确实允许在传输和提示列表中完成此操作,在我的情况下,我的 two_factor_types
The main takeaway is that paramiko does allow for this to be done in the transport and prompts list and in my case i was missing the publickey method in my two_factor_types
def _auth(self, username, password, pkey, *args):
self.password = password
saved_exception = None
two_factor = False
allowed_types = set()
two_factor_types = {'keyboard-interactive', 'password', 'publickey'}
参考文献:
Paramiko/Python:键盘交互认证
https://github.com/paramiko/paramiko/pull/467
https://github.com/paramiko/paramiko/pull/467/commits/dae916f7bd6723cee95891778baff51ef45532ee
http://docs.paramiko.org/en/stable/api/transport.html
我建议尝试一些类似的东西auth_interactive_dumb
I would recommend trying something along the lines of auth_interactive_dumb
auth_interactive_dumb(username, handler=None, submethods='')
以交互方式向服务器进行身份验证,但比较笨拙.只需将提示和/或说明打印到标准输出并发回响应.这适用于通过密钥实现部分身份验证然后用户必须输入 2fac 令牌的情况.
Authenticate 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.
有关更完整的示例,请参阅下面的摘录和链接
For more full example see excerpt and link below
class SSHClient(paramiko.SSHClient):
duo_auth = False
def handler(self, title, instructions, prompt_list):
answers = []
global duo_auth
if title.startswith('Duo two-factor login'):
duo_auth = True
raise SSHException("Expecting one field only.")
for prompt_, _ in prompt_list:
prompt = prompt_.strip().lower()
if prompt.startswith('password'):
answers.append(self.password)
elif prompt.startswith('verification'):
answers.append(self.totp)
elif prompt.startswith('passcode'):
answers.append(self.totp)
else:
raise ValueError('Unknown prompt: {}'.format(prompt_))
return answers
def auth_interactive(self, username, handler):
if not self.totp:
raise ValueError('Need a verification code for 2fa.')
self._transport.auth_interactive(username, handler)
def _auth(self, username, password, pkey, *args):
self.password = password
saved_exception = None
two_factor = False
allowed_types = set()
two_factor_types = {'keyboard-interactive', 'password', 'publickey'}
agent = paramiko.Agent()
try:
agent_keys = agent.get_keys()
# if len(agent_keys) == 0:
# return
except:
pass
for key in agent_keys:
logging.info("Trying ssh-agent key %s" % hexlify(key.get_fingerprint()))
try:
self._transport.auth_publickey(username, key)
logging.info("... success!")
return
except paramiko.SSHException as e:
logging.info("... nope.")
saved_exception = e
if pkey is not None:
logging.info('Trying publickey authentication')
try:
allowed_types = set(
self._transport.auth_publickey(username, pkey)
)
two_factor = allowed_types & two_factor_types
if not two_factor:
return
except paramiko.SSHException as e:
saved_exception = e
if duo_auth or two_factor:
logging.info('Trying 2fa interactive auth')
return self.auth_interactive(username, self.handler)
if password is not None:
logging.info('Trying password authentication')
try:
self._transport.auth_password(username, password)
return
except paramiko.SSHException as e:
saved_exception = e
allowed_types = set(getattr(e, 'allowed_types', []))
two_factor = allowed_types & two_factor_types
assert saved_exception is not None
raise saved_exception
这篇关于使用 Paramiko 在 Python 中使用 2FA 进行 SSH的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!